@ynck/rendux 0.92.1 → 0.93.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,19 +1,36 @@
1
- ## Changelog
1
+ # Changelog
2
+
3
+ ## v0.93.3
4
+
5
+ - fixed nested x-for rendering
6
+
7
+ ## v0.93.1
8
+
9
+ - x-key diffing algorithm is now reusing DOM nodes thus greatly optimzing rendering (up to 50% faster!)
10
+
11
+ ## v0.93.0
12
+
13
+ - remove plugins, focusing on core library
14
+ - Fixed state and rendering target coupling, now state and rendering target element are completely decoupled. The function requires both state and target now : rendux(state, element)
15
+
16
+ ## v0.92.0
2
17
 
3
- ### v0.92
4
18
  - Mini Evaluator is now directly embeded in `rendux`
5
19
 
6
- ### v0.91
20
+ ## v0.91.0
21
+
7
22
  - Added conditional logging system for plugins and core features
8
23
  - Removed x-click-outside plugin
9
24
  - Fixed plugin attribute handling and re-rendering
10
25
  - Improved plugin logging control via `logs` attribute
11
26
 
12
- ### v0.86
27
+ ## v0.86.0
28
+
13
29
  - Inlined plugin core into `rendux.js`, removing dependence on external raw-render-core.js.
14
30
  - Added chainable plugin API: `.use()`, `.process()`, `plugins`, and `parsePluginCall` directly on `rendux`.
15
31
  - Fixed plugin attribute lookup to avoid invalid CSS selector issues for `render.plugin`.
16
32
 
17
- ### v0.84
33
+ ## v0.84.0
34
+
18
35
  - Hidden elements (`x-if`) now reside in an in-memory `DocumentFragment` instead of a `<template>`, preventing hidden nodes from cluttering the DOM or shadow DOM.
19
- - `<template x-for>` loops are now tracked from both the live DOM and the hidden fragment for correct re-rendering.
36
+ - `<template x-for>` loops are now tracked from both the live DOM and the hidden fragment for correct re-rendering.
@@ -29,4 +29,4 @@ This Commercial EULA ("Agreement") is a legal contract between you ("Licensee")
29
29
  8. Entire Agreement.
30
30
  This Agreement constitutes the entire agreement between the parties and supersedes all prior or contemporaneous agreements.
31
31
 
32
- For inquiries or to purchase a commercial license, contact: ynck.chrl@pm.me
32
+ For inquiries or to purchase a commercial license, contact: ynck.chrl@protonmail.com
package/README.md CHANGED
@@ -1,23 +1,139 @@
1
- ![rendux Logo](./rendux-logo.jpg)
2
-
3
- Source-available (non‑commercial).
4
- Commercial licenses via EULA (See EULA-COMMERCIAL.md for commercial use).
5
-
1
+ ![rendux Logo](https://raw.githubusercontent.com/ynck-chrl/rendux/master/rendux-logo.jpg)
6
2
 
7
3
  # rendux
8
4
 
9
5
  ## Introduction
10
6
 
11
- `rendux` is a lightweight dependency-free templating engine designed for use within Custom Elements (Web Components). It provides reactive templating features through directives, making it easy to create dynamic, state-driven components without a full framework.
7
+ `rendux` is a lightweight dependency-free templating engine designed for use within Custom Elements (Web Components) and plain HTML. It provides reactive templating features through directives, making it easy to create dynamic, state-driven components without a full framework.
12
8
 
13
9
  ### Size
14
10
 
15
- - Minified: 13.0 KB (ESM), 13.0 KB (CJS)
16
- - Gzipped: 4.7 KB
11
+ - Minified: 13.2 KB (ESM), 13.3 KB (CJS)
12
+ - Gzipped: ~4.7 KB
13
+
14
+ ## Installation
15
+
16
+ ### Via NPM
17
+
18
+ ```bash
19
+ npm install @ynck/rendux
20
+ ```
21
+
22
+ Then import in your project:
23
+
24
+ ```js
25
+ // ES Module (recommended)
26
+ import { rendux } from '@ynck/rendux';
27
+
28
+ // CommonJS
29
+ const { rendux } = require('@ynck/rendux');
30
+ ```
31
+
32
+ ### Via CDN (No Build Step)
33
+
34
+ #### unpkg
35
+
36
+ ```html
37
+ <!-- ES Module -->
38
+ <script type="module">
39
+ import { rendux } from 'https://unpkg.com/@ynck/rendux@latest/dist/rendux.min.js';
40
+ </script>
41
+
42
+ <!-- Or use specific version -->
43
+ <script type="module">
44
+ import { rendux } from 'https://unpkg.com/@ynck/rendux@0.93.3/dist/rendux.min.js';
45
+ </script>
46
+ ```
47
+
48
+ #### esm.sh
49
+
50
+ ```html
51
+ <!-- Latest version -->
52
+ <script type="module">
53
+ import { rendux } from 'https://esm.sh/@ynck/rendux@latest'
54
+ </script>
55
+
56
+ <!-- Or with specific version -->
57
+ <script type="module">
58
+ import { rendux } from 'https://esm.sh/@ynck/rendux@0.93.3'
59
+ </script>
60
+ ```
61
+
62
+ #### Skypack
63
+
64
+ ```html
65
+ <script type="module">
66
+ import { rendux } from 'https://cdn.skypack.dev/@ynck/rendux'
67
+ </script>
68
+
69
+ <!-- Or with specific version -->
70
+ <script type="module">
71
+ import { rendux } from 'https://cdn.skypack.dev/@ynck/rendux@0.93.3';
72
+ </script>
73
+ ```
74
+
75
+ ### From Source (Local Development)
76
+
77
+ Clone the repository and use the source directly:
78
+
79
+ ```bash
80
+ git clone https://github.com/ynck-chrl/rendux.git
81
+ cd rendux
82
+ ```
83
+
84
+ Then import from source:
85
+
86
+ ```html
87
+ <script type="module">
88
+ import { rendux } from './src/rendux.js';
89
+ </script>
90
+ ```
91
+
92
+ Or if using in a Node.js project:
93
+
94
+ ```js
95
+ import { rendux } from './path/to/rendux/src/rendux.js';
96
+ ```
97
+
98
+ ### Build Outputs
99
+
100
+ When installed via NPM, rendux provides multiple build outputs:
101
+
102
+ - **ESM (ES Modules)**: `dist/rendux.min.js` - For modern browsers and bundlers
103
+ - **CommonJS**: `dist/rendux.cjs.min.js` - For Node.js and legacy tools
104
+ - **Source Maps**: Available for all builds (`.map` files)
105
+
106
+ The `package.json` exports field automatically selects the correct build:
107
+
108
+ ```json
109
+ {
110
+ "exports": {
111
+ ".": {
112
+ "import": "./dist/rendux.min.js",
113
+ "require": "./dist/rendux.cjs.min.js"
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ### TypeScript Support
120
+
121
+ TypeScript definitions are not yet included. You can create a declaration file:
122
+
123
+ ```typescript
124
+ // rendux.d.ts
125
+ declare module 'rendux' {
126
+ export function rendux(state: any, element: HTMLElement): void;
127
+ export function use(plugin: any): typeof rendux;
128
+ // Add other exports as needed
129
+ }
130
+ ```
131
+
132
+
17
133
 
18
134
  ## Core Concepts
19
135
 
20
- `rendux` is called within a Custom Element's context and operates on its shadow DOM. It provides several directives to handle common UI patterns:
136
+ `rendux` is a function that takes a state object and a DOM element, then processes templating directives within that element. It provides several directives to handle common UI patterns:
21
137
 
22
138
  - `x-if` for conditional rendering
23
139
  - `x-for` for list rendering
@@ -25,7 +141,7 @@ Commercial licenses via EULA (See EULA-COMMERCIAL.md for commercial use).
25
141
  - `x-*` for dynamic attributes
26
142
  - `render` for text content interpolation
27
143
  - `@event` for event handling
28
- - `render.plugin` for plugin execution
144
+ - `render.plugin` for custom plugin execution
29
145
 
30
146
  ## Logging System
31
147
 
@@ -40,60 +156,79 @@ rendux includes a flexible logging system controlled by the `logs` attribute. Yo
40
156
  ```
41
157
 
42
158
  Available log categories:
43
- - `plugins`: Plugin execution and results
44
159
  - `for`: x-for loop processing
45
160
  - `if`: x-if conditional rendering
46
161
  - `attr`: x-attr attribute processing
47
162
  - `class`: x-class processing
48
163
  - `render`: Text content rendering
164
+ - `plugins`: Plugin execution and results
49
165
 
50
166
  ## Basic Usage
51
167
 
52
168
  ### With Custom Elements (Web Components)
53
169
 
54
- #### With Shadow DOM
170
+ #### With Internal State and Shadow DOM
55
171
 
56
172
  ```js
173
+ import { rendux } from 'rendux';
174
+
57
175
  class MyElement extends HTMLElement {
58
176
  constructor() {
59
177
  super();
60
178
  this.attachShadow({ mode: 'open' });
179
+
180
+ // Internal component state
181
+ this.state = {
182
+ title: 'User List',
183
+ users: [
184
+ { name: 'Alice', active: true },
185
+ { name: 'Bob', active: false }
186
+ ]
187
+ };
61
188
  }
62
189
 
63
- // Example component state
64
- users = [
65
- { name: 'Alice', active: true },
66
- { name: 'Bob', active: false }
67
- ];
68
-
69
190
  connectedCallback() {
70
191
  this.shadowRoot.innerHTML = `
71
192
  <div>
72
- <h2 render="title">User List</h2>
193
+ <h2 render="title"></h2>
73
194
  <template x-for="user of users">
74
195
  <div x-class="(active, user.active)">
75
196
  <span render="user.name"></span>
197
+ <button @click="toggleUser(user)">Toggle</button>
76
198
  </div>
77
199
  </template>
78
200
  </div>
79
201
  `;
80
- rendux.call(this);
202
+ // Pass state and element - renders into shadowRoot
203
+ rendux(this.state, this);
204
+ }
205
+
206
+ toggleUser(user) {
207
+ user.active = !user.active;
208
+ // Re-render after state change
209
+ rendux(this.state, this);
81
210
  }
82
211
  }
83
212
  ```
84
213
 
85
- #### Without Shadow DOM (plain Light DOM)
214
+ #### With Internal State and Light DOM
86
215
 
87
216
  ```js
88
- import { rendux } from './src/rendux.js';
217
+ import { rendux } from 'rendux';
89
218
 
90
219
  class MyElement extends HTMLElement {
91
- // Example component state
92
- title = 'User List';
93
- users = [
94
- { name: 'Alice', active: true },
95
- { name: 'Bob', active: false }
96
- ];
220
+ constructor() {
221
+ super();
222
+
223
+ // Internal component state
224
+ this.state = {
225
+ title: 'User List',
226
+ users: [
227
+ { name: 'Alice', active: true },
228
+ { name: 'Bob', active: false }
229
+ ]
230
+ };
231
+ }
97
232
 
98
233
  connectedCallback() {
99
234
  // Render directly into light DOM (no shadow root)
@@ -108,20 +243,60 @@ class MyElement extends HTMLElement {
108
243
  </template>
109
244
  </div>
110
245
  `;
111
- rendux.call(this); // uses 'this' as root in Light DOM
246
+ // Pass state and element - renders into element itself (light DOM)
247
+ rendux(this.state, this);
248
+ }
249
+
250
+ toggleUser(user) {
251
+ user.active = !user.active;
252
+ // Re-render after state change
253
+ rendux(this.state, this);
254
+ }
255
+ }
256
+ ```
257
+
258
+ #### With External/Global State
259
+
260
+ ```js
261
+ import { rendux } from 'rendux';
262
+ import { state } from './state.js'; // External state (could be from a store)
263
+
264
+ class MyGlobalUsers extends HTMLElement {
265
+ connectedCallback() {
266
+ this.innerHTML = `
267
+ <div>
268
+ <h2 render="title"></h2>
269
+ <template x-for="(user, userIndex) of users">
270
+ <div x-class="(active, user.active) (non-active, !user.active)">
271
+ <span render="user.name"></span>
272
+ <span render="userIndex"></span>
273
+ <button @click="toggleUser(user)">Toggle</button>
274
+ </div>
275
+ </template>
276
+ </div>
277
+ `;
278
+ // Use external state with this element
279
+ rendux(state, this);
112
280
  }
113
281
 
114
282
  toggleUser(user) {
115
283
  user.active = !user.active;
116
- rendux.call(this); // re-render after state change
284
+ // Re-render with external state
285
+ rendux(state, this);
117
286
  }
118
287
  }
119
288
  ```
120
289
 
290
+ **Key Points:**
291
+ - **First argument**: Always the state object (can be `this.state`, an external state, or any object/array)
292
+ - **Second argument**: Always the DOM element (`this` in Custom Elements)
293
+ - **Rendering target**: If element has `shadowRoot`, renders into it; otherwise renders into the element itself (light DOM)
294
+ - **Template expressions**: Reference properties directly from the state object (e.g., `title`, `users`, not `state.title`)
295
+
121
296
 
122
297
  ### With Plain HTML
123
298
 
124
- You can also use rendux with plain HTML by creating a context object:
299
+ You can also use rendux with plain HTML:
125
300
 
126
301
  ```html
127
302
  <!DOCTYPE html>
@@ -130,32 +305,32 @@ You can also use rendux with plain HTML by creating a context object:
130
305
  <script type="module">
131
306
  import { rendux } from './src/rendux.js';
132
307
 
133
- // Create a context object with your data and methods
134
- const context = {
308
+ // Create a state object with your data
309
+ const state = {
135
310
  title: 'User List',
136
311
  users: [
137
312
  { name: 'Alice', active: true },
138
313
  { name: 'Bob', active: false }
139
- ],
140
-
141
- // Add methods if needed
142
- toggleUser(user) {
143
- user.active = !user.active;
144
- rendux.call(this); // Re-render after state change
145
- }
314
+ ]
146
315
  };
147
316
 
148
- // Call rendux with the context
149
- rendux.call(context);
317
+ // Get the container element
318
+ const container = document.querySelector('#app');
319
+
320
+ // Initial render
321
+ rendux(state, container);
322
+
323
+ // To update after state changes:
324
+ // state.users.push({ name: 'Charlie', active: true });
325
+ // rendux(state, container);
150
326
  </script>
151
327
  </head>
152
328
  <body>
153
- <div>
329
+ <div id="app">
154
330
  <h2 render="title">User List</h2>
155
331
  <template x-for="user of users">
156
332
  <div x-class="(active, user.active)">
157
333
  <span render="user.name"></span>
158
- <button @click="toggleUser(user)">Toggle</button>
159
334
  </div>
160
335
  </template>
161
336
  </div>
@@ -164,19 +339,19 @@ You can also use rendux with plain HTML by creating a context object:
164
339
  ```
165
340
 
166
341
  **Key Points for Plain HTML Usage:**
167
- - Create a context object with your data and methods
168
- - Use `rendux.call(context)` to bind the context
169
- - Call `rendux.call(context)` again after state changes to re-render
170
- - The context object becomes the `this` reference in all expressions
342
+ - Create a state object with your data
343
+ - Pass the state and container element to `rendux(state, element)`
344
+ - Call `rendux(state, element)` again after state changes to re-render
345
+ - Event handlers (like `@click`) can reference methods if you add them to the element's prototype or use inline expressions
171
346
 
172
347
  ### With CommonJS (Node.js)
173
348
 
174
349
  ```js
175
350
  // Import using CommonJS
176
- const { rendux } = require('./dist/rendux.cjs');
351
+ const { rendux } = require('@ynck/rendux');
177
352
 
178
- // Create a context with your data
179
- const context = {
353
+ // Create a state object with your data
354
+ const state = {
180
355
  title: 'Server Context',
181
356
  users: [
182
357
  { name: 'Alice', active: true },
@@ -188,7 +363,7 @@ const context = {
188
363
  // For Node.js, use with a DOM implementation like jsdom
189
364
  const { JSDOM } = require('jsdom');
190
365
  const dom = new JSDOM(`
191
- <div>
366
+ <div id="app">
192
367
  <h2 render="title">User List</h2>
193
368
  <template x-for="user of users">
194
369
  <div x-class="(active, user.active)">
@@ -201,8 +376,11 @@ const dom = new JSDOM(`
201
376
  // Set up global document for rendux
202
377
  global.document = dom.window.document;
203
378
 
204
- // Call rendux with the context
205
- rendux.call(context, dom.window.document.body);
379
+ // Get the container element
380
+ const container = dom.window.document.querySelector('#app');
381
+
382
+ // Call rendux with state and element
383
+ rendux(state, container);
206
384
  ```
207
385
 
208
386
  **When to Use `rendux.cjs`:**
@@ -219,7 +397,58 @@ Use the CommonJS build (`dist/rendux.cjs`) in these scenarios:
219
397
 
220
398
  For modern projects with native ESM support, prefer the ESM build (`dist/rendux.js`) instead.
221
399
 
400
+ ### With Reactive State Libraries
401
+
402
+ rendux works seamlessly with reactive state management libraries. Here's an example using a Proxy-based reactive state:
403
+
404
+ ```js
405
+ import { rendux } from 'rendux';
406
+ import { restate } from 'restate'; // Or any reactive state library
407
+
408
+ // Create reactive state
409
+ const state = restate({
410
+ title: 'User List',
411
+ users: [
412
+ { name: 'Alice', active: true },
413
+ { name: 'Bob', active: false }
414
+ ]
415
+ });
416
+
417
+ class MyComponent extends HTMLElement {
418
+ connectedCallback() {
419
+ this.innerHTML = `
420
+ <div>
421
+ <h2 render="title"></h2>
422
+ <template x-for="user of users">
423
+ <div x-class="(active, user.active)">
424
+ <span render="user.name"></span>
425
+ <button @click="toggleUser(user)">Toggle</button>
426
+ </div>
427
+ </template>
428
+ </div>
429
+ `;
430
+
431
+ // Initial render
432
+ rendux(state, this);
433
+
434
+ // Set up automatic re-rendering on state changes
435
+ state.$onChange(() => {
436
+ rendux(state, this);
437
+ });
438
+ }
439
+
440
+ toggleUser(user) {
441
+ // Just mutate - reactive state will trigger re-render
442
+ user.active = !user.active;
443
+ }
444
+ }
445
+ ```
222
446
 
447
+ **Benefits of Reactive State:**
448
+ - Automatic re-rendering when state changes
449
+ - No need to manually call `rendux()` after every update
450
+ - Works with any Proxy-based reactive library
451
+ - rendux handles circular references and deep objects safely
223
452
 
224
453
  ## Directives
225
454
 
@@ -318,70 +547,182 @@ The event object is automatically available in handlers as `event`.
318
547
 
319
548
  ## Plugin System
320
549
 
321
- rendux includes an integrated plugin system that allows you to extend templating functionality.
550
+ rendux includes a powerful plugin system that allows you to extend templating functionality with custom directives.
322
551
 
323
552
  ### Using Plugins
324
553
 
554
+ Plugins are registered using the `use()` method, which is chainable:
555
+
325
556
  ```js
326
- import { rendux } from './src/rendux.js';
327
- import { i18nPlugin } from './src/plugins/plugin-i18n.js';
328
- import { xTooltip } from './src/plugins/plugin-x-tooltip.js';
557
+ import { rendux } from 'rendux';
558
+ import { myCustomPlugin } from './plugins/my-plugin.js';
559
+
560
+ // Register plugin (chainable)
561
+ rendux.use(myCustomPlugin);
329
562
 
330
- // Register plugins (chainable)
563
+ // Or chain multiple plugins
331
564
  rendux
332
- .use(i18nPlugin({
333
- translations: {
334
- en: { greeting: 'Hello!' },
335
- fr: { greeting: 'Bonjour!' }
336
- }
337
- }))
338
- .use(xTooltip);
565
+ .use(plugin1)
566
+ .use(plugin2)
567
+ .use(plugin3);
339
568
  ```
340
569
 
341
- ### Using Plugin Directives
570
+ ### Using Plugin Directives in Templates
571
+
572
+ Once registered, plugins can be used with the `render.pluginName` attribute:
342
573
 
343
574
  ```html
344
- <!-- i18n Plugin -->
575
+ <!-- Using a custom formatter plugin -->
576
+ <span render.format="user.name">Default Name</span>
577
+
578
+ <!-- Using a custom i18n plugin -->
345
579
  <p render.i18n="greeting">Hello!</p>
346
580
 
347
- <!-- Tooltip Plugin -->
348
- <button x-tooltip="'Click to save'">Save</button>
581
+ <!-- Generic plugin syntax -->
582
+ <span render.plugin="myPlugin('arg1', 'arg2')">Fallback</span>
349
583
  ```
350
584
 
351
- ### Building Custom Plugins
585
+ ### Creating Custom Plugins
352
586
 
353
- A plugin is an object with the following structure:
587
+ A plugin is an object with specific properties and methods:
354
588
 
355
589
  ```js
356
590
  export const myPlugin = {
357
- // Required properties
358
- name: 'pluginName', // Used in render.pluginName or x-pluginName
359
- target: 'attribute', // Optional: 'attribute' for x-* plugins
360
- execute(el, value, ctx) {
361
- // Main plugin logic
362
- return 'result'; // Optional: string to use as element content
591
+ // Required: Plugin name (used in render.pluginName)
592
+ name: 'myPlugin',
593
+
594
+ // Required: Main execution function
595
+ execute(value, ...args) {
596
+ // Process the value and return result
597
+ // The returned string becomes the element's textContent
598
+ return processedValue;
363
599
  },
364
-
365
- // Optional lifecycle hooks (in order of execution)
600
+
601
+ // Optional: Called once before any plugin executes
366
602
  onBeforeRender(root, component) {
367
- // Called ONCE at the start of rendux()
368
- // Use for initialization, setup, or capturing initial state
603
+ // Initialize plugin state
604
+ // Access to root element and component context
605
+ },
606
+
607
+ // Optional: Called before each element execution
608
+ onBeforeExecute(element, rawValue, context) {
609
+ // Setup for this specific element
610
+ // Can modify element attributes, add classes, etc.
611
+ },
612
+
613
+ // Optional: Called after each element execution
614
+ onAfterExecute(element, rawValue, context) {
615
+ // Cleanup or post-processing for this element
369
616
  },
370
617
 
371
- onBeforeExecute(el, rawValue, ctx) {
372
- // Called BEFORE each plugin execution on an element
373
- // Use for element-specific setup or validation
618
+ // Optional: Called once after all plugins execute (can be async)
619
+ async onAfterRender(root, context) {
620
+ // Final cleanup or async operations
621
+ // This is the only lifecycle hook that supports async
622
+ }
623
+ };
624
+ ```
625
+
626
+ ### Plugin Example: Text Formatter
627
+
628
+ Here's a complete example of a text formatting plugin:
629
+
630
+ ```js
631
+ export const formatPlugin = {
632
+ name: 'format',
633
+
634
+ execute(value, format = 'uppercase') {
635
+ if (typeof value !== 'string') {
636
+ value = String(value);
637
+ }
638
+
639
+ switch(format) {
640
+ case 'uppercase':
641
+ return value.toUpperCase();
642
+ case 'lowercase':
643
+ return value.toLowerCase();
644
+ case 'capitalize':
645
+ return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
646
+ case 'reverse':
647
+ return value.split('').reverse().join('');
648
+ default:
649
+ return value;
650
+ }
651
+ }
652
+ };
653
+
654
+ // Register the plugin
655
+ rendux.use(formatPlugin);
656
+ ```
657
+
658
+ Usage in template:
659
+
660
+ ```html
661
+ <span render.format="user.name, 'capitalize'">John Doe</span>
662
+ <span render.format="title, 'uppercase'">Hello World</span>
663
+ ```
664
+
665
+ ### Plugin Example: Conditional Formatter
666
+
667
+ A more advanced plugin with lifecycle hooks:
668
+
669
+ ```js
670
+ export const conditionalPlugin = {
671
+ name: 'showIf',
672
+
673
+ onBeforeRender(root, component) {
674
+ // Store original content for elements using this plugin
675
+ this.originalContent = new WeakMap();
374
676
  },
375
677
 
376
- onAfterExecute(el, rawValue, ctx) {
377
- // Called AFTER each plugin execution on an element
378
- // Use for cleanup or post-processing of element changes
678
+ execute(condition, trueValue, falseValue = '') {
679
+ // Evaluate condition and return appropriate value
680
+ return condition ? trueValue : falseValue;
379
681
  },
380
682
 
381
- onAfterRender(root, ctx) {
382
- // Called ONCE at the end of rendux()
383
- // Use for final cleanup or post-render operations
384
- // This hook is async - can return a Promise
683
+ onAfterExecute(element, rawValue, context) {
684
+ // Add a custom class based on the condition
685
+ const condition = rawValue.split(',')[0].trim();
686
+ if (condition) {
687
+ element.classList.add('visible');
688
+ } else {
689
+ element.classList.remove('visible');
690
+ }
691
+ }
692
+ };
693
+ ```
694
+
695
+ ### Plugin Best Practices
696
+
697
+ 1. **Validate Inputs**: Always validate and sanitize plugin inputs
698
+ 2. **Handle Errors Gracefully**: Use try-catch to prevent plugin errors from breaking rendering
699
+ 3. **Keep Plugins Focused**: Each plugin should do one thing well
700
+ 4. **Document Parameters**: Clearly document what arguments your plugin expects
701
+ 5. **Return Strings**: The `execute()` method should return a string (or value that converts to string)
702
+ 6. **Avoid Side Effects**: Minimize DOM manipulation outside of the element being processed
703
+
704
+ ### Security Considerations for Plugins
705
+
706
+ When creating plugins, be mindful of security:
707
+
708
+ ```js
709
+ export const safePlugin = {
710
+ name: 'safe',
711
+
712
+ execute(userInput) {
713
+ // Validate input type
714
+ if (typeof userInput !== 'string') {
715
+ return '';
716
+ }
717
+
718
+ // Sanitize HTML entities
719
+ return userInput.replace(/[<>&"']/g, char => ({
720
+ '<': '&lt;',
721
+ '>': '&gt;',
722
+ '&': '&amp;',
723
+ '"': '&quot;',
724
+ "'": '&#39;'
725
+ })[char]);
385
726
  }
386
727
  };
387
728
  ```
@@ -432,31 +773,140 @@ render="window.location = 'evil.com'" ✗ (window not accessible)
432
773
 
433
774
  **Safe Template Processing**
434
775
  - Template elements (`<template x-for>`) use `.content` property, not innerHTML
435
- - Plugin system processes attributes safely without executing embedded scripts
776
+ - Attributes are processed safely without executing embedded scripts
436
777
  - Event handlers are properly scoped and don't use string-to-function conversion
437
778
 
438
779
  ### Best Practices for Secure Usage
439
780
 
440
- 1. **Validate Plugin Inputs**: When creating custom plugins, validate and sanitize inputs
781
+ 1. **Validate Inputs**: Always validate and sanitize user inputs before adding them to state
441
782
  2. **Context Isolation**: Each component maintains its own isolated context
442
783
  3. **No Script Injection**: User data in expressions is evaluated safely, not executed as code
784
+ 4. **Safe State Updates**: Mutate state objects directly and call `rendux()` to re-render, avoiding innerHTML manipulation
443
785
 
444
786
  ```js
445
- // Safe plugin example
446
- export const safePlugin = {
787
+ // Safe state update example
788
+ class MyComponent extends HTMLElement {
789
+ constructor() {
790
+ super();
791
+ this.state = {
792
+ userInput: ''
793
+ };
794
+ }
795
+
796
+ handleInput(event) {
797
+ // Validate and sanitize
798
+ const value = event.target.value;
799
+ if (typeof value === 'string' && value.length < 100) {
800
+ this.state.userInput = value.trim();
801
+ rendux(this.state, this); // Safe re-render
802
+ }
803
+ }
804
+ }
805
+ ```
806
+
807
+ This security model makes rendux suitable for applications handling user-generated content while maintaining the flexibility of a templating engine.
808
+
809
+ ### Security Audit Summary
810
+
811
+ rendux has been designed and audited to ensure maximum security for web applications:
812
+
813
+ #### Core Engine Security ✓
814
+
815
+ **No Dangerous Functions**
816
+ - ✓ No `eval()` or `Function()` constructor used
817
+ - ✓ No `innerHTML` for dynamic content
818
+ - ✓ No `outerHTML` manipulation
819
+ - ✓ No `document.write()`
820
+
821
+ **Safe Content Rendering**
822
+ - ✓ All dynamic content uses `textContent` (auto-escapes HTML)
823
+ - ✓ User input like `<script>alert('XSS')</script>` is rendered as escaped text, never executed
824
+ - ✓ Plugin outputs are also rendered via `textContent`
825
+
826
+ **Sandboxed Expression Evaluator**
827
+ - ✓ Custom parser with whitelisted globals only
828
+ - ✓ Allowed: `Math`, `Date`, `Number`, `String`, `Boolean`, `JSON`, `Array`, `parseInt`, `parseFloat`, `isFinite`
829
+ - ✗ Blocked: `window`, `document`, `eval`, `fetch`, `XMLHttpRequest`, `localStorage`, `location`
830
+
831
+ **Safe Template Processing**
832
+ - ✓ Templates use `.content` property (no innerHTML)
833
+ - ✓ Event handlers use sandboxed evaluator (no string-to-function conversion)
834
+
835
+ #### Plugin System Security
836
+
837
+ **Framework Security ✓**
838
+ - ✓ Plugin output automatically escaped via `textContent`
839
+ - ✓ Plugin execution wrapped in try-catch for graceful error handling
840
+ - ✓ Plugins cannot access dangerous globals
841
+
842
+ **Developer Responsibility ⚠️**
843
+
844
+ Plugin developers **must validate and sanitize** all inputs in their `execute()` method:
845
+
846
+ ```js
847
+ // ✓ SECURE: Properly validated plugin
848
+ export const securePlugin = {
447
849
  name: 'format',
448
- execute(value) {
449
- // Validate and sanitize input
450
- if (typeof value !== 'string') return '';
451
- return value.replace(/[<>&"']/g, char => ({
850
+
851
+ execute(value, options = {}) {
852
+ // 1. Type validation
853
+ if (typeof value !== 'string') {
854
+ console.warn('Invalid input type, expected string');
855
+ return '';
856
+ }
857
+
858
+ // 2. Length validation
859
+ if (value.length > 10000) {
860
+ console.warn('Input too long');
861
+ return value.substring(0, 10000);
862
+ }
863
+
864
+ // 3. Sanitization (if needed for your use case)
865
+ const sanitized = value.replace(/[<>&"']/g, char => ({
452
866
  '<': '&lt;', '>': '&gt;', '&': '&amp;',
453
867
  '"': '&quot;', "'": '&#39;'
454
868
  })[char]);
869
+
870
+ // 4. Safe processing
871
+ return sanitized.toUpperCase();
872
+ }
873
+ };
874
+
875
+ // ✗ INSECURE: No validation
876
+ export const insecurePlugin = {
877
+ name: 'unsafe',
878
+ execute(value) {
879
+ // Dangerous: No type checking, no validation
880
+ return value.toUpperCase(); // Crashes if value is not a string
455
881
  }
456
882
  };
457
883
  ```
458
884
 
459
- This security model makes rendux suitable for applications handling user-generated content while maintaining the flexibility of a templating engine.
885
+ **Plugin Security Checklist**
886
+
887
+ When creating plugins, ensure you:
888
+
889
+ 1. ✓ **Validate input types** - Check `typeof` before processing
890
+ 2. ✓ **Validate input length** - Prevent resource exhaustion
891
+ 3. ✓ **Sanitize if needed** - Escape special characters for your use case
892
+ 4. ✓ **Handle errors gracefully** - Use try-catch to prevent crashes
893
+ 5. ✓ **Return safe values** - Always return strings or values that convert to string
894
+ 6. ✓ **Document requirements** - Clearly document expected input types and formats
895
+ 7. ✓ **Avoid DOM manipulation** - Minimize direct DOM changes outside the element
896
+ 8. ✓ **Test with malicious input** - Test with XSS payloads, large inputs, invalid types
897
+
898
+ #### Security Guarantees
899
+
900
+ | Feature | Core Engine | Plugin System |
901
+ |---------|-------------|---------------|
902
+ | XSS Prevention | ✓ Guaranteed | ✓ Framework protected, ⚠️ validate inputs |
903
+ | Code Injection | ✓ Blocked | ✓ Blocked |
904
+ | HTML Escaping | ✓ Automatic | ✓ Automatic |
905
+ | Global Access | ✓ Whitelisted only | ✓ Whitelisted only |
906
+ | Expression Safety | ✓ Sandboxed | ✓ Sandboxed |
907
+ | Input Validation | ✓ Type-checked | ⚠️ Developer responsibility |
908
+
909
+ **Verdict**: rendux provides a **secure foundation** for web applications. The core engine is hardened against XSS and code injection attacks. Plugin developers must follow security best practices and validate all inputs to maintain the security guarantee.
460
910
 
461
911
  ## License
462
912
 
@@ -1,2 +1,2 @@
1
- /*! rendux v0.92.1 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={Math:Math,Date:Date,Number:Number,String:String,Boolean:Boolean,JSON:JSON,parseInt:parseInt,parseFloat:parseFloat,isFinite:isFinite,Array:Array};function t(t,r={}){const n=function(e){const t=[];let r=0;for(;r<e.length;){let n=e[r];if(/\s/.test(n)){r++;continue}if('"'===n||"'"===n){const o=n;let s="";for(r++;r<e.length&&e[r]!==o;)"\\"===e[r]?(r++,r<e.length&&(s+=e[r++])):s+=e[r++];r++,t.push({type:"string",value:s});continue}if(/[0-9]/.test(n)||"."===n&&/[0-9]/.test(e[r+1])){let n=r;for(;r<e.length&&/[0-9]/.test(e[r]);)r++;if("."===e[r])for(r++;r<e.length&&/[0-9]/.test(e[r]);)r++;const o=parseFloat(e.slice(n,r));t.push({type:"number",value:o});continue}if(/[a-zA-Z_$]/.test(n)){let n=r;for(r++;r<e.length&&/[a-zA-Z0-9_$]/.test(e[r]);)r++;const o=e.slice(n,r);"true"===o||"false"===o?t.push({type:"boolean",value:"true"===o}):"null"===o?t.push({type:"null",value:null}):t.push({type:"identifier",value:o});continue}const o=e.substr(r,2),s=e.substr(r,3);if(["===","!=="].includes(s))t.push({type:"operator",value:s}),r+=3;else if(["==","!=",">=","<=","&&","||"].includes(o))t.push({type:"operator",value:o}),r+=2;else{if(!["+","-","*","/","%",">","<","!","?",":","(",")","[","]",".",","].includes(n))throw new Error(`Invalid character '${n}' in expression`);t.push({type:"operator",value:n}),r++}}return t.push({type:"EOF"}),t}(t);let o=0;function s(){return n[o]||{type:"EOF"}}function a(){return n[o++]||{type:"EOF"}}function i(e,t){const r=s();if(r.type!==e||void 0!==t&&r.value!==t)throw new Error(`Expected ${t||e} but got ${r.value}`);return a(),r}function l(e){const t=s();return"operator"===t.type&&t.value===e}const c=u();if("EOF"!==s().type)throw new Error(`Unexpected token: ${s().value}`);return c;function u(){let e=function(){let e=f();for(;l("||");){a();const t=f();e=e||t}return e}();if(l("?")){a();const t=u();i("operator",":");const r=u();e=e?t:r}return e}function f(){let e=p();for(;l("&&");){a();const t=p();e=e&&t}return e}function p(){let e=d();for(;["==","!=","===","!=="].includes(s().value);){const t=a().value,r=d();switch(t){case"==":e=e==r;break;case"!=":e=e!=r;break;case"===":e=e===r;break;case"!==":e=e!==r}}return e}function d(){let e=h();for(;[">","<",">=","<="].includes(s().value);){const t=a().value,r=h();switch(t){case">":e=e>r;break;case"<":e=e<r;break;case">=":e=e>=r;break;case"<=":e=e<=r}}return e}function h(){let e=x();for(;["+","-"].includes(s().value);){const t=a().value,r=x();e="+"===t?e+r:e-r}return e}function x(){let e=g();for(;["*","/","%"].includes(s().value);){const t=a().value,r=g();switch(t){case"*":e*=r;break;case"/":e/=r;break;case"%":e%=r}}return e}function g(){if(["!","+","-"].includes(s().value)){const e=a().value,t=g();switch(e){case"!":return!t;case"+":return+t;case"-":return-t}}return function(){const t=s();if("number"===t.type||"string"===t.type||"boolean"===t.type||"null"===t.type)return a(),t.value;if("identifier"===t.type){a();let n=function(t){if(t in r)return r[t];if(t in e)return e[t];if(null!=r.this&&t in r.this)return r.this[t];throw new Error(`Unknown identifier: ${t}`)}(t.value);for(;;)if(l(".")){a();const e=i("identifier").value;n=null==n?void 0:n[e]}else if(l("[")){a();const e=u();i("operator","]"),n=null==n?void 0:n[e]}else{if(!l("("))break;{a();const e=[];if(!l(")"))do{e.push(u())}while(l(",")&&a());if(i("operator",")"),"function"!=typeof n)throw new Error(`'${t.value}' is not a function`);n=n.apply(r.this,e)}}return n}if(l("(")){a();const e=u();return i("operator",")"),e}throw new Error(`Unexpected token: ${t.value}`)}()}}const r=new Map;function n(e){if(!e||!e.name)throw new Error("Invalid plugin");r.set(e.name,e)}function o(e,t={}){const n=e.match(/^([a-zA-Z_$][\w$]*)\s*\(\s*(.*)\s*\)$/);if(!n)return null;const o=n[1],s=r.get(o);if(!s)return null;const a=n[2].trim();if(""===a)return{plugin:s,args:[]};return{plugin:s,args:a.split(/\s*,\s*/).map(e=>{if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);if(/^-?\d+(?:\.\d+)?$/.test(e))return parseFloat(e);if(e in t)return t[e];throw new Error(`Unknown identifier: ${e}`)})}}async function s(e=document,t={}){const n=Array.from(e.querySelectorAll("*")).filter(e=>Array.from(e.attributes).some(e=>e.name.startsWith("render.")));for(const e of n)for(const n of Array.from(e.attributes)){if(!n.name.startsWith("render."))continue;const s=n.name,a=n.value.trim();let i,l,c;if("render.plugin"===s){let e;try{e=o(a,t)}catch(e){console.error(e);continue}if(!e)continue;i=e.plugin,l=e.args}else{const e=s.slice(7);if(i=r.get(e),!i)continue;if(a.startsWith(e+"(")){let e;try{e=o(a,t)}catch(e){console.error(e);continue}if(!e)continue;i=e.plugin,l=e.args}else l=[a]}"function"==typeof i.onBeforeExecute&&i.onBeforeExecute(e,a,t);try{c=i.execute(...l)}catch(e){continue}e.textContent=null==c?"":String(c),"function"==typeof i.onAfterExecute&&i.onAfterExecute(e,a,t)}for(const n of r.values())"function"==typeof n.onAfterRender&&await n.onAfterRender(e,t)}function rendux(e=this.shadowRoot||this){const n=this;for(const t of r.values())"function"==typeof t.onBeforeRender&&t.onBeforeRender(e,n);const o=document,a=n.getAttribute("logs"),i=Boolean(a),l=a?new Set(a.split(",").map(e=>e.trim().toLowerCase())):new Set,c=e=>l.has("all")||l.has(e.toLowerCase())||l.has(("x-"+e).toLowerCase()),u=i&&l.has("plugins"),f=(...e)=>console.warn(...e);n._xRenderCache||(n._xRenderCache=new Map),n._xRenderOriginalText||(n._xRenderOriginalText=new WeakMap),n._xIfData||(n._xIfData=new WeakMap),n._cloneContext||(n._cloneContext=new WeakMap),n._xClassCache||(n._xClassCache=new Map),n._xForCache||(n._xForCache=new WeakMap),n.__mirrorContainer||(n.__mirrorContainer=o.createDocumentFragment());const p={};function d(e){let t=e;for(;t;){const e=n._cloneContext.get(t);if(e)return n.getAttribute("logs")&&n.getAttribute("logs").includes("plugins")&&console.log("Found context for element:",t,e),e;t=t.parentNode}return n.getAttribute("logs")&&n.getAttribute("logs").includes("plugins")&&console.log("Using default component context:",n),n}function h(e,r){if(!e)return!0;const n=d(r);try{return t(e,{this:n,...p})}catch(t){return void f(`[evaluate] "${e}" failed in`,n,t)}}Object.getOwnPropertyNames(Object.getPrototypeOf(n)).forEach(e=>{"function"==typeof n[e]&&"constructor"!==e&&(p[e]=n[e].bind(n))}),p.plugins=r;const x=i&&c("for");x&&console.groupCollapsed("x-for");Array.from(e.querySelectorAll("template[x-for]")).concat(Array.from(n.__mirrorContainer.querySelectorAll("template[x-for]"))).forEach(e=>{let t,r,s;e._xForMeta||(e._xForMeta={parent:e.parentNode,next:e.nextSibling},n.__mirrorContainer.appendChild(e));try{({loopVar:t,indexVar:r,arrayPath:s}=function(e){const t=e.match(/^\s*(?:\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*\)|([^,\s()]+))\s+(?:in|of)\s+(.+)$/);if(!t)throw new Error("Invalid x-for: "+e);return{loopVar:t[1]||t[3],indexVar:t[2]||null,arrayPath:t[4].trim()}}(e.getAttribute("x-for")))}catch(e){return void f("[x-for]",e.message)}const a=function(e,t){const r=d(t),n=e.match(/([^[.\]]+)|\[(\d+)\]/g);if(n)return n.reduce((e,t)=>{if(null!=e){if(t.startsWith("[")){const r=parseInt(t.slice(1,-1),10);return Array.isArray(e)?e[r]:void 0}return e[t]}},r)}(s,e);if(!Array.isArray(a))return void f(`[x-for] expected array at ${s}`,a);x&&console.log(`iterating ${s} → length ${a.length}`);const i=function(e){return JSON.stringify(e)}(a),l=n._xForCache.get(e);l&&l.arrayRef===a&&l.length===a.length&&l.signature===i?x&&console.log(" → skipped (no change)"):(n._xForCache.set(e,{arrayRef:a,length:a.length,signature:i}),e._forClones&&e._forClones.forEach(e=>e.remove()),e._forClones=[],a.forEach((s,a)=>{x&&console.groupCollapsed(` index ${a}`);const i=o.createDocumentFragment();i.appendChild(e.content.cloneNode(!0));const l=(c=t,u=s,f=r,p=a,new Proxy(n,{has:(e,t)=>t===c||f&&t===f||t in e,get:(e,t)=>t===c?u:f&&t===f?p:e[t]}));var c,u,f,p,d,h;d=i,h=l,n._cloneContext.set(d,h),d.querySelectorAll("*").forEach(e=>n._cloneContext.set(e,h));const g=n._xRenderCache,y=n._xRenderOriginalText;for(n._xRenderCache=new Map,n._xRenderOriginalText=new WeakMap,rendux.call(n,i),n._xRenderCache=g,n._xRenderOriginalText=y;i.firstChild;){const t=i.firstChild;e._xForMeta.parent.insertBefore(t,e._xForMeta.next),e._forClones.push(t)}x&&console.groupEnd()}))}),x&&console.groupEnd();const g=i&&c("if");Array.from(e.querySelectorAll("[x-if]")).concat(Array.from(n.__mirrorContainer.querySelectorAll("[x-if]"))).forEach(e=>{const t=e.getAttribute("x-if")?.trim(),r=!t||h(t,e);g&&console.groupCollapsed("x-if",e,t,"→",r);let s=n._xIfData.get(e);s||(s={placeholder:o.createComment("x-if placeholder"),isHidden:!1},n._xIfData.set(e,s)),r||s.isHidden?r&&s.isHidden&&(s.placeholder.parentNode.replaceChild(e,s.placeholder),s.isHidden=!1):(e.parentNode.replaceChild(s.placeholder,e),n.__mirrorContainer.appendChild(e),s.isHidden=!0),g&&console.groupEnd()}),0===n._xRenderCache.size&&Array.from(e.querySelectorAll("[render]")).forEach(e=>{const t=e.getAttribute("render")?.trim();t&&(n._xRenderCache.set(e,t),n._xRenderOriginalText.set(e,e.textContent))}),Array.from(e.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null==t||n._xClassCache.has(e)||n._xClassCache.set(e,t)});const y=i&&c("attr");Array.from(e.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(t=>{if(t.name.startsWith("x-")&&!["x-for","x-key","x-class","x-if","x-hidden"].includes(t.name)){const o=t.name,s=r.get(o);if(s&&"attribute"===s.target){const r=h(t.value.trim(),e);try{u&&console.log("[plugin]",`Executing ${o} with:`,{element:e,value:r,component:n}),s.execute(e,r,n)}catch(e){u&&console.error("[plugin]",`Error executing ${o}:`,e)}}else{const r=t.name.slice(2),n=h(t.value.trim(),e);y&&console.groupCollapsed("x-attr",e,r,"=",n),n?e.setAttribute(r,String(n)):e.removeAttribute(r),y&&console.groupEnd()}}})});const m=i&&c("class");m&&console.groupCollapsed("x-class");for(const[e,t]of n._xClassCache){m&&console.log("element →",e);const r=t.trim();if(r.includes("(")){const t=/\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)/g;n._xClassDynamicValues||(n._xClassDynamicValues=new Map);(n._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));const o=new Set;let s;for(;s=t.exec(r);){const t=s[1].trim(),r=s[2].trim();let n;n=/^[\w\-\s]+$/.test(t)?t:h(t,e);const a=Boolean(h(r,e));let i=[];"string"==typeof n?i=n.split(/\s+/).filter(Boolean):Array.isArray(n)?i=n:n&&"object"==typeof n&&(i=Object.keys(n).filter(e=>n[e])),a&&i.forEach(t=>e.classList.add(t)),i.forEach(e=>o.add(e)),m&&console.log(` ${t} → [${i.join(" ")}] (= ${a})`)}n._xClassDynamicValues.set(e,Array.from(o))}else{n._xClassDynamicValues||(n._xClassDynamicValues=new Map);let t;(n._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));let o=!0;if(!r.includes(",")&&/[?:]/.test(r))t=h(r,e);else{const n=r.indexOf(","),s=n>=0?r.slice(0,n).trim():r,a=n>=0?r.slice(n+1).trim():"true";o=Boolean(h(a,e)),t=s}const s=[];"string"==typeof t?s.push(...t.split(/\s+/).filter(Boolean)):Array.isArray(t)?s.push(...t):t&&"object"==typeof t&&s.push(...Object.keys(t).filter(e=>t[e])),s.forEach(t=>{o?e.classList.add(t):e.classList.remove(t)}),m&&console.log(` x-class ${r} →`,s,`ok=${o}`),n._xClassDynamicValues.set(e,s)}}m&&console.groupEnd();const _=i&&c("render");_&&console.groupCollapsed("render");for(const[e,t]of n._xRenderCache){const r=n._xIfData.get(e);if(r&&r.isHidden)continue;_&&console.log("element →",e,"expr=",t);const o=t.indexOf(","),s=o<0?t:t.slice(0,o).trim(),a=h(o<0?"true":t.slice(o+1).trim(),e);let i;if(a){let t=h(s,e);if(void 0===t)i=n._xRenderOriginalText.get(e)||"";else if(null!=t&&"object"==typeof t)try{i=JSON.stringify(t,null,2)}catch{i=String(t)}else i=null==t||"boolean"==typeof t?"":String(t)}else i=n._xRenderOriginalText.get(e)||"";e.textContent!==i&&(e.textContent=i),_&&console.log(` → "${i}" (cond=${a})`)}_&&console.groupEnd(),Array.from(e.querySelectorAll("*")).forEach(e=>{const t=n._xIfData.get(e);t&&t.isHidden||[["render","render"]].forEach(([t,r])=>{e.hasAttribute(t)&&!c(r)&&e.removeAttribute(t)})}),function e(r){Array.from(r.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(r=>{if(r.name.startsWith("@")){const o=r.name.slice(1),s=r.value.trim();e._xEventListeners&&e._xEventListeners[o]&&e.removeEventListener(o,e._xEventListeners[o]),e._xEventListeners||(e._xEventListeners={});const a=d(e),i=new Proxy(a,{get:(e,t)=>t in e?e[t]:n[t]}),l=function(e){try{return t(s,{this:i,event:e,...p})}catch(e){f(`[rendux @${o}] Error evaluating: ${s}`,e)}};e.addEventListener(o,l),e._xEventListeners[o]=l}})}),Array.from(r.querySelectorAll("slot")).forEach(t=>{(t.assignedElements?t.assignedElements({flatten:!0}):[]).forEach(t=>{e(t)})})}(e),s(e,n)}rendux.use=function(e){return n(e),rendux},"undefined"!=typeof module&&module.exports&&(module.exports={rendux:rendux,use:n,process:s,plugins:r,parsePluginCall:o}),exports.parsePluginCall=o,exports.plugins=r,exports.process=s,exports.rendux=rendux,exports.use=n;
1
+ /*! rendux v0.93.3 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={Math:Math,Date:Date,Number:Number,String:String,Boolean:Boolean,JSON:JSON,parseInt:parseInt,parseFloat:parseFloat,isFinite:isFinite,Array:Array};function t(t,r={}){const n=function(e){const t=[];let r=0;for(;r<e.length;){let n=e[r];if(/\s/.test(n)){r++;continue}if('"'===n||"'"===n){const o=n;let s="";for(r++;r<e.length&&e[r]!==o;)"\\"===e[r]?(r++,r<e.length&&(s+=e[r++])):s+=e[r++];r++,t.push({type:"string",value:s});continue}if(/[0-9]/.test(n)||"."===n&&/[0-9]/.test(e[r+1])){let n=r;for(;r<e.length&&/[0-9]/.test(e[r]);)r++;if("."===e[r])for(r++;r<e.length&&/[0-9]/.test(e[r]);)r++;const o=parseFloat(e.slice(n,r));t.push({type:"number",value:o});continue}if(/[a-zA-Z_$]/.test(n)){let n=r;for(r++;r<e.length&&/[a-zA-Z0-9_$]/.test(e[r]);)r++;const o=e.slice(n,r);"true"===o||"false"===o?t.push({type:"boolean",value:"true"===o}):"null"===o?t.push({type:"null",value:null}):t.push({type:"identifier",value:o});continue}const o=e.substr(r,2),s=e.substr(r,3);if(["===","!=="].includes(s))t.push({type:"operator",value:s}),r+=3;else if(["==","!=",">=","<=","&&","||"].includes(o))t.push({type:"operator",value:o}),r+=2;else{if(!["+","-","*","/","%",">","<","!","?",":","(",")","[","]",".",","].includes(n))throw new Error(`Invalid character '${n}' in expression`);t.push({type:"operator",value:n}),r++}}return t.push({type:"EOF"}),t}(t);let o=0;function s(){return n[o]||{type:"EOF"}}function l(){return n[o++]||{type:"EOF"}}function a(e,t){const r=s();if(r.type!==e||void 0!==t&&r.value!==t)throw new Error(`Expected ${t||e} but got ${r.value}`);return l(),r}function i(e){const t=s();return"operator"===t.type&&t.value===e}const c=u();if("EOF"!==s().type)throw new Error(`Unexpected token: ${s().value}`);return c;function u(){let e=function(){let e=f();for(;i("||");){l();const t=f();e=e||t}return e}();if(i("?")){l();const t=u();a("operator",":");const r=u();e=e?t:r}return e}function f(){let e=d();for(;i("&&");){l();const t=d();e=e&&t}return e}function d(){let e=h();for(;["==","!=","===","!=="].includes(s().value);){const t=l().value,r=h();switch(t){case"==":e=e==r;break;case"!=":e=e!=r;break;case"===":e=e===r;break;case"!==":e=e!==r}}return e}function h(){let e=p();for(;[">","<",">=","<="].includes(s().value);){const t=l().value,r=p();switch(t){case">":e=e>r;break;case"<":e=e<r;break;case">=":e=e>=r;break;case"<=":e=e<=r}}return e}function p(){let e=x();for(;["+","-"].includes(s().value);){const t=l().value,r=x();e="+"===t?e+r:e-r}return e}function x(){let e=g();for(;["*","/","%"].includes(s().value);){const t=l().value,r=g();switch(t){case"*":e*=r;break;case"/":e/=r;break;case"%":e%=r}}return e}function g(){if(["!","+","-"].includes(s().value)){const e=l().value,t=g();switch(e){case"!":return!t;case"+":return+t;case"-":return-t}}return function(){const t=s();if("number"===t.type||"string"===t.type||"boolean"===t.type||"null"===t.type)return l(),t.value;if("identifier"===t.type){l();let n=function(t){if(t in r)return r[t];if(t in e)return e[t];if(null!=r.this&&t in r.this)return r.this[t];throw new Error(`Unknown identifier: ${t}`)}(t.value);for(;;)if(i(".")){l();const e=a("identifier").value;n=null==n?void 0:n[e]}else if(i("[")){l();const e=u();a("operator","]"),n=null==n?void 0:n[e]}else{if(!i("("))break;{l();const e=[];if(!i(")"))do{e.push(u())}while(i(",")&&l());if(a("operator",")"),"function"!=typeof n)throw new Error(`'${t.value}' is not a function`);n=n.apply(r.this,e)}}return n}if(i("(")){l();const e=u();return a("operator",")"),e}throw new Error(`Unexpected token: ${t.value}`)}()}}const r=new Map;function n(e){if(!e||!e.name)throw new Error("Invalid plugin");r.set(e.name,e)}function o(e,t={}){const n=e.match(/^([a-zA-Z_$][\w$]*)\s*\(\s*(.*)\s*\)$/);if(!n)return null;const o=n[1],s=r.get(o);if(!s)return null;const l=n[2].trim();if(""===l)return{plugin:s,args:[]};return{plugin:s,args:l.split(/\s*,\s*/).map(e=>{if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);if(/^-?\d+(?:\.\d+)?$/.test(e))return parseFloat(e);if(e in t)return t[e];throw new Error(`Unknown identifier: ${e}`)})}}async function s(e=document,t={}){const n=Array.from(e.querySelectorAll("*")).filter(e=>Array.from(e.attributes).some(e=>e.name.startsWith("render.")));for(const e of n)for(const n of Array.from(e.attributes)){if(!n.name.startsWith("render."))continue;const s=n.name,l=n.value.trim();let a,i,c;if("render.plugin"===s){let e;try{e=o(l,t)}catch(e){console.error(e);continue}if(!e)continue;a=e.plugin,i=e.args}else{const e=s.slice(7);if(a=r.get(e),!a)continue;if(l.startsWith(e+"(")){let e;try{e=o(l,t)}catch(e){console.error(e);continue}if(!e)continue;a=e.plugin,i=e.args}else i=[l]}"function"==typeof a.onBeforeExecute&&a.onBeforeExecute(e,l,t);try{c=a.execute(...i)}catch(e){continue}e.textContent=null==c?"":String(c),"function"==typeof a.onAfterExecute&&a.onAfterExecute(e,l,t)}for(const n of r.values())"function"==typeof n.onAfterRender&&await n.onAfterRender(e,t)}function rendux(e,n){const o=e,l=n;let a=n.shadowRoot||n;if(!a)throw new Error("rendux requires a DOM element as the second argument");for(const e of r.values())"function"==typeof e.onBeforeRender&&e.onBeforeRender(a,o);const i=document,c=l.getAttribute&&l.getAttribute("logs"),u=Boolean(c),f=c?new Set(c.split(",").map(e=>e.trim().toLowerCase())):new Set,d=e=>f.has("all")||f.has(e.toLowerCase())||f.has(("x-"+e).toLowerCase()),h=u&&f.has("plugins"),p=(...e)=>console.warn(...e);l._xRenderCache||(l._xRenderCache=new Map),l._xRenderOriginalText||(l._xRenderOriginalText=new WeakMap),l._xIfData||(l._xIfData=new WeakMap),l._cloneContext||(l._cloneContext=new WeakMap),l._xClassCache||(l._xClassCache=new Map),l._xForCache||(l._xForCache=new WeakMap),l.__mirrorContainer||(l.__mirrorContainer=i.createDocumentFragment());const x={};function g(e){let t=e;for(;t;){const e=l._cloneContext.get(t);if(e)return l.getAttribute&&l.getAttribute("logs")&&l.getAttribute("logs").includes("plugins")&&console.log("Found context for element:",t,e),e;t=t.parentNode}return l.getAttribute&&l.getAttribute("logs")&&l.getAttribute("logs").includes("plugins")&&console.log("Using default component context:",o),o}function y(e,r){if(!e)return!0;const n=g(r);try{return t(e,{this:n,...x})}catch(t){return void p(`[evaluate] "${e}" failed in`,n,t)}}function m(e,t){const r=g(t),n=e.match(/([^[.\]]+)|\[(\d+)\]/g);if(n)return n.reduce((e,t)=>{if(null!=e){if(t.startsWith("[")){const r=parseInt(t.slice(1,-1),10);return Array.isArray(e)?e[r]:void 0}return e[t]}},r)}function A(e){const t=e.match(/^\s*(?:\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*\)|([^,\s()]+))\s+(?:in|of)\s+(.+)$/);if(!t)throw new Error("Invalid x-for: "+e);return{loopVar:t[1]||t[3],indexVar:t[2]||null,arrayPath:t[4].trim()}}function C(e,t,r,n,o){return new Proxy(e,{has:(e,r)=>r===t||n&&r===n||r in e,get:(e,s)=>s===t?r:n&&s===n?o:e[s]})}function _(e,t){l._cloneContext.set(e,t),e.querySelectorAll("*").forEach(e=>l._cloneContext.set(e,t))}l&&Object.getPrototypeOf(l)&&Object.getOwnPropertyNames(Object.getPrototypeOf(l)).forEach(e=>{"function"==typeof l[e]&&"constructor"!==e&&(x[e]=l[e].bind(l))}),x.plugins=r;const E=u&&d("for");function b(e){try{const t=new WeakSet;return JSON.stringify(e,(e,r)=>{if("function"!=typeof r&&!e.startsWith("$")){if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r}})}catch(t){return`${e.length}-${e[0]?.name||""}-${e[e.length-1]?.name||""}`}}E&&console.groupCollapsed("x-for");let v=new Set,w=!0;for(;w;){const e=Array.from(a.querySelectorAll("template[x-for]")).concat(Array.from(l.__mirrorContainer.querySelectorAll("template[x-for]"))).filter(e=>!v.has(e));if(0===e.length){w=!1;break}e.forEach(e=>{let r,n,s;v.add(e),e._xForMeta||(e._xForMeta={parent:e.parentNode,next:e.nextSibling},l.__mirrorContainer.appendChild(e));try{({loopVar:r,indexVar:n,arrayPath:s}=A(e.getAttribute("x-for")))}catch(e){return void p("[x-for]",e.message)}const a=m(s,e);if(!Array.isArray(a))return void p(`[x-for] expected array at ${s}`,a);E&&console.log(`iterating ${s} → length ${a.length}`);const c=e.content.cloneNode(!0).querySelector("*"),u=c&&c.hasAttribute("x-key");if(!u){const t=b(a),r=l._xForCache.get(e);if(r&&r.arrayRef===a&&r.length===a.length&&r.signature===t)return void(E&&console.log(" → skipped (no change)"));l._xForCache.set(e,{arrayRef:a,length:a.length,signature:t})}if(u&&e._forClones&&e._forClones.length>0){E&&console.log(" → using x-key diffing");const s=c.getAttribute("x-key"),u=new Map;e._forClones.forEach(e=>{if(1===e.nodeType){const t=e.getAttribute("x-key");t&&u.set(t,e)}});const f=[],d=new Set;a.forEach((a,c)=>{E&&console.groupCollapsed(` index ${c}`);const h=C(o,r,a,n,c);let g;try{g=t(s,{this:h,...x})}catch(e){p("[x-for] Error evaluating x-key:",e),g=c}const y=String(g);if(u.has(y)){const e=u.get(y);d.add(e),E&&console.log(` → reusing node for key: ${y}`),l._cloneContext.set(e,h),f.push(e)}else{E&&console.log(` → creating new node for key: ${y}`);const t=i.createDocumentFragment();t.appendChild(e.content.cloneNode(!0));const r=t.firstElementChild;if(r){t.removeChild(r),r.setAttribute("x-key",y),_(r,h);(r.hasAttribute("render")?[r,...r.querySelectorAll("[render]")]:r.querySelectorAll("[render]")).forEach(e=>{const t=e.getAttribute("render")?.trim();t&&(l._xRenderCache.set(e,t),l._xRenderOriginalText.set(e,e.textContent))});(r.hasAttribute("x-class")?[r,...r.querySelectorAll("[x-class]")]:r.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null!=t&&l._xClassCache.set(e,t)}),f.push(r)}}E&&console.groupEnd()}),e._forClones.forEach(e=>{if(!d.has(e)){if(E&&console.log(" → removing unused node"),1===e.nodeType){(e.hasAttribute("render")?[e,...e.querySelectorAll("[render]")]:e.querySelectorAll("[render]")).forEach(e=>{l._xRenderCache.delete(e),l._xRenderOriginalText.delete(e)});(e.hasAttribute("x-class")?[e,...e.querySelectorAll("[x-class]")]:e.querySelectorAll("[x-class]")).forEach(e=>{l._xClassCache.delete(e),l._xClassDynamicValues.delete(e)})}e.remove()}}),f.forEach((t,r)=>{const n=e._xForMeta.parent,o=r<f.length-1?f[r+1]:e._xForMeta.next;t.parentNode&&t.nextSibling===o||n.insertBefore(t,o)}),e._forClones=f}else E&&u&&console.log(" → first render with x-key"),E&&!u&&console.log(" → no x-key, full re-render"),e._forClones&&e._forClones.forEach(e=>{if(1===e.nodeType){(e.hasAttribute("render")?[e,...e.querySelectorAll("[render]")]:e.querySelectorAll("[render]")).forEach(e=>{l._xRenderCache.delete(e),l._xRenderOriginalText.delete(e)});(e.hasAttribute("x-class")?[e,...e.querySelectorAll("[x-class]")]:e.querySelectorAll("[x-class]")).forEach(e=>{l._xClassCache.delete(e),l._xClassDynamicValues.delete(e)})}e.remove()}),e._forClones=[],a.forEach((s,a)=>{E&&console.groupCollapsed(` index ${a}`);const c=i.createDocumentFragment();c.appendChild(e.content.cloneNode(!0));const f=C(o,r,s,n,a);if(_(c,f),u){const e=c.firstElementChild;if(e){const r=e.getAttribute("x-key");if(r)try{const n=t(r,{this:f,...x});e.setAttribute("x-key",String(n))}catch(t){e.setAttribute("x-key",String(a))}}}for(;c.firstChild;){const t=c.firstChild;if(e._xForMeta.parent.insertBefore(t,e._xForMeta.next),e._forClones.push(t),1===t.nodeType){(t.hasAttribute("render")?[t,...t.querySelectorAll("[render]")]:t.querySelectorAll("[render]")).forEach(e=>{const t=e.getAttribute("render")?.trim();t&&(l._xRenderCache.set(e,t),l._xRenderOriginalText.set(e,e.textContent))});(t.hasAttribute("x-class")?[t,...t.querySelectorAll("[x-class]")]:t.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null!=t&&l._xClassCache.set(e,t)})}}E&&console.groupEnd()})})}E&&console.groupEnd();const S=u&&d("if");Array.from(a.querySelectorAll("[x-if]")).concat(Array.from(l.__mirrorContainer.querySelectorAll("[x-if]"))).forEach(e=>{const t=e.getAttribute("x-if")?.trim(),r=!t||y(t,e);S&&console.groupCollapsed("x-if",e,t,"→",r);let n=l._xIfData.get(e);n||(n={placeholder:i.createComment("x-if placeholder"),isHidden:!1},l._xIfData.set(e,n)),r||n.isHidden?r&&n.isHidden&&(n.placeholder.parentNode.replaceChild(e,n.placeholder),n.isHidden=!1):(e.parentNode.replaceChild(n.placeholder,e),l.__mirrorContainer.appendChild(e),n.isHidden=!0),S&&console.groupEnd()}),Array.from(a.querySelectorAll("[render]")).forEach(e=>{if(!l._xRenderCache.has(e)){const t=e.getAttribute("render")?.trim();t&&(l._xRenderCache.set(e,t),l._xRenderOriginalText.set(e,e.textContent))}}),Array.from(a.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null==t||l._xClassCache.has(e)||l._xClassCache.set(e,t)});const $=u&&d("attr");Array.from(a.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(t=>{if(t.name.startsWith("x-")&&!["x-for","x-key","x-class","x-if","x-hidden"].includes(t.name)){const n=t.name,s=r.get(n);if(s&&"attribute"===s.target){const r=y(t.value.trim(),e);try{h&&console.log("[plugin]",`Executing ${n} with:`,{element:e,value:r,component:o}),s.execute(e,r,o)}catch(e){h&&console.error("[plugin]",`Error executing ${n}:`,e)}}else{const r=t.name.slice(2),n=y(t.value.trim(),e);$&&console.groupCollapsed("x-attr",e,r,"=",n),n?e.setAttribute(r,String(n)):e.removeAttribute(r),$&&console.groupEnd()}}})});const k=u&&d("class");k&&console.groupCollapsed("x-class");for(const[e,t]of l._xClassCache){k&&console.log("element →",e);const r=t.trim();if(r.includes("(")){const t=/\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)/g;l._xClassDynamicValues||(l._xClassDynamicValues=new Map);(l._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));const n=new Set;let o;for(;o=t.exec(r);){const t=o[1].trim(),r=o[2].trim();let s;s=/^[\w\-\s]+$/.test(t)?t:y(t,e);const l=Boolean(y(r,e));let a=[];"string"==typeof s?a=s.split(/\s+/).filter(Boolean):Array.isArray(s)?a=s:s&&"object"==typeof s&&(a=Object.keys(s).filter(e=>s[e])),l&&a.forEach(t=>e.classList.add(t)),a.forEach(e=>n.add(e)),k&&console.log(` ${t} → [${a.join(" ")}] (= ${l})`)}l._xClassDynamicValues.set(e,Array.from(n))}else{l._xClassDynamicValues||(l._xClassDynamicValues=new Map);let t;(l._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));let n=!0;if(!r.includes(",")&&/[?:]/.test(r))t=y(r,e);else{const o=r.indexOf(","),s=o>=0?r.slice(0,o).trim():r,l=o>=0?r.slice(o+1).trim():"true";n=Boolean(y(l,e)),t=s}const o=[];"string"==typeof t?o.push(...t.split(/\s+/).filter(Boolean)):Array.isArray(t)?o.push(...t):t&&"object"==typeof t&&o.push(...Object.keys(t).filter(e=>t[e])),o.forEach(t=>{n?e.classList.add(t):e.classList.remove(t)}),k&&console.log(` x-class ${r} →`,o,`ok=${n}`),l._xClassDynamicValues.set(e,o)}}k&&console.groupEnd();const q=u&&d("render");q&&console.groupCollapsed("render");for(const[e,t]of l._xRenderCache){const r=l._xIfData.get(e);if(r&&r.isHidden)continue;q&&console.log("element →",e,"expr=",t);const n=t.indexOf(","),o=n<0?t:t.slice(0,n).trim(),s=y(n<0?"true":t.slice(n+1).trim(),e);let a;if(s){let t=y(o,e);if(void 0===t)a=l._xRenderOriginalText.get(e)||"";else if(null!=t&&"object"==typeof t)try{a=JSON.stringify(t,null,2)}catch{a=String(t)}else a=null==t||"boolean"==typeof t?"":String(t)}else a=l._xRenderOriginalText.get(e)||"";e.textContent!==a&&(e.textContent=a),q&&console.log(` → "${a}" (cond=${s})`)}q&&console.groupEnd(),Array.from(a.querySelectorAll("*")).forEach(e=>{const t=l._xIfData.get(e);t&&t.isHidden||[["render","render"]].forEach(([t,r])=>{e.hasAttribute(t)&&!d(r)&&e.removeAttribute(t)})}),function e(r){Array.from(r.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(r=>{if(r.name.startsWith("@")){const n=r.name.slice(1),s=r.value.trim();e._xEventListeners&&e._xEventListeners[n]&&e.removeEventListener(n,e._xEventListeners[n]),e._xEventListeners||(e._xEventListeners={});const l=g(e),a=new Proxy(l,{get:(e,t)=>t in e?e[t]:o[t]}),i=function(e){try{return t(s,{this:a,event:e,...x})}catch(e){p(`[rendux @${n}] Error evaluating: ${s}`,e)}};e.addEventListener(n,i),e._xEventListeners[n]=i}})}),Array.from(r.querySelectorAll("slot")).forEach(t=>{(t.assignedElements?t.assignedElements({flatten:!0}):[]).forEach(t=>{e(t)})})}(a),s(a,o)}rendux.use=function(e){return n(e),rendux},"undefined"!=typeof module&&module.exports&&(module.exports={rendux:rendux,use:n,process:s,plugins:r,parsePluginCall:o}),exports.parsePluginCall=o,exports.plugins=r,exports.process=s,exports.rendux=rendux,exports.use=n;
@@ -1,2 +1,2 @@
1
- /*! rendux v0.92.1 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
- const e={Math:Math,Date:Date,Number:Number,String:String,Boolean:Boolean,JSON:JSON,parseInt:parseInt,parseFloat:parseFloat,isFinite:isFinite,Array:Array};function t(t,r={}){const n=function(e){const t=[];let r=0;for(;r<e.length;){let n=e[r];if(/\s/.test(n)){r++;continue}if('"'===n||"'"===n){const o=n;let s="";for(r++;r<e.length&&e[r]!==o;)"\\"===e[r]?(r++,r<e.length&&(s+=e[r++])):s+=e[r++];r++,t.push({type:"string",value:s});continue}if(/[0-9]/.test(n)||"."===n&&/[0-9]/.test(e[r+1])){let n=r;for(;r<e.length&&/[0-9]/.test(e[r]);)r++;if("."===e[r])for(r++;r<e.length&&/[0-9]/.test(e[r]);)r++;const o=parseFloat(e.slice(n,r));t.push({type:"number",value:o});continue}if(/[a-zA-Z_$]/.test(n)){let n=r;for(r++;r<e.length&&/[a-zA-Z0-9_$]/.test(e[r]);)r++;const o=e.slice(n,r);"true"===o||"false"===o?t.push({type:"boolean",value:"true"===o}):"null"===o?t.push({type:"null",value:null}):t.push({type:"identifier",value:o});continue}const o=e.substr(r,2),s=e.substr(r,3);if(["===","!=="].includes(s))t.push({type:"operator",value:s}),r+=3;else if(["==","!=",">=","<=","&&","||"].includes(o))t.push({type:"operator",value:o}),r+=2;else{if(!["+","-","*","/","%",">","<","!","?",":","(",")","[","]",".",","].includes(n))throw new Error(`Invalid character '${n}' in expression`);t.push({type:"operator",value:n}),r++}}return t.push({type:"EOF"}),t}(t);let o=0;function s(){return n[o]||{type:"EOF"}}function a(){return n[o++]||{type:"EOF"}}function i(e,t){const r=s();if(r.type!==e||void 0!==t&&r.value!==t)throw new Error(`Expected ${t||e} but got ${r.value}`);return a(),r}function l(e){const t=s();return"operator"===t.type&&t.value===e}const c=u();if("EOF"!==s().type)throw new Error(`Unexpected token: ${s().value}`);return c;function u(){let e=function(){let e=f();for(;l("||");){a();const t=f();e=e||t}return e}();if(l("?")){a();const t=u();i("operator",":");const r=u();e=e?t:r}return e}function f(){let e=p();for(;l("&&");){a();const t=p();e=e&&t}return e}function p(){let e=d();for(;["==","!=","===","!=="].includes(s().value);){const t=a().value,r=d();switch(t){case"==":e=e==r;break;case"!=":e=e!=r;break;case"===":e=e===r;break;case"!==":e=e!==r}}return e}function d(){let e=h();for(;[">","<",">=","<="].includes(s().value);){const t=a().value,r=h();switch(t){case">":e=e>r;break;case"<":e=e<r;break;case">=":e=e>=r;break;case"<=":e=e<=r}}return e}function h(){let e=x();for(;["+","-"].includes(s().value);){const t=a().value,r=x();e="+"===t?e+r:e-r}return e}function x(){let e=g();for(;["*","/","%"].includes(s().value);){const t=a().value,r=g();switch(t){case"*":e*=r;break;case"/":e/=r;break;case"%":e%=r}}return e}function g(){if(["!","+","-"].includes(s().value)){const e=a().value,t=g();switch(e){case"!":return!t;case"+":return+t;case"-":return-t}}return function(){const t=s();if("number"===t.type||"string"===t.type||"boolean"===t.type||"null"===t.type)return a(),t.value;if("identifier"===t.type){a();let n=function(t){if(t in r)return r[t];if(t in e)return e[t];if(null!=r.this&&t in r.this)return r.this[t];throw new Error(`Unknown identifier: ${t}`)}(t.value);for(;;)if(l(".")){a();const e=i("identifier").value;n=null==n?void 0:n[e]}else if(l("[")){a();const e=u();i("operator","]"),n=null==n?void 0:n[e]}else{if(!l("("))break;{a();const e=[];if(!l(")"))do{e.push(u())}while(l(",")&&a());if(i("operator",")"),"function"!=typeof n)throw new Error(`'${t.value}' is not a function`);n=n.apply(r.this,e)}}return n}if(l("(")){a();const e=u();return i("operator",")"),e}throw new Error(`Unexpected token: ${t.value}`)}()}}const r=new Map;function n(e){if(!e||!e.name)throw new Error("Invalid plugin");r.set(e.name,e)}function o(e,t={}){const n=e.match(/^([a-zA-Z_$][\w$]*)\s*\(\s*(.*)\s*\)$/);if(!n)return null;const o=n[1],s=r.get(o);if(!s)return null;const a=n[2].trim();if(""===a)return{plugin:s,args:[]};return{plugin:s,args:a.split(/\s*,\s*/).map(e=>{if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);if(/^-?\d+(?:\.\d+)?$/.test(e))return parseFloat(e);if(e in t)return t[e];throw new Error(`Unknown identifier: ${e}`)})}}async function s(e=document,t={}){const n=Array.from(e.querySelectorAll("*")).filter(e=>Array.from(e.attributes).some(e=>e.name.startsWith("render.")));for(const e of n)for(const n of Array.from(e.attributes)){if(!n.name.startsWith("render."))continue;const s=n.name,a=n.value.trim();let i,l,c;if("render.plugin"===s){let e;try{e=o(a,t)}catch(e){console.error(e);continue}if(!e)continue;i=e.plugin,l=e.args}else{const e=s.slice(7);if(i=r.get(e),!i)continue;if(a.startsWith(e+"(")){let e;try{e=o(a,t)}catch(e){console.error(e);continue}if(!e)continue;i=e.plugin,l=e.args}else l=[a]}"function"==typeof i.onBeforeExecute&&i.onBeforeExecute(e,a,t);try{c=i.execute(...l)}catch(e){continue}e.textContent=null==c?"":String(c),"function"==typeof i.onAfterExecute&&i.onAfterExecute(e,a,t)}for(const n of r.values())"function"==typeof n.onAfterRender&&await n.onAfterRender(e,t)}function rendux(e=this.shadowRoot||this){const n=this;for(const t of r.values())"function"==typeof t.onBeforeRender&&t.onBeforeRender(e,n);const o=document,a=n.getAttribute("logs"),i=Boolean(a),l=a?new Set(a.split(",").map(e=>e.trim().toLowerCase())):new Set,c=e=>l.has("all")||l.has(e.toLowerCase())||l.has(("x-"+e).toLowerCase()),u=i&&l.has("plugins"),f=(...e)=>console.warn(...e);n._xRenderCache||(n._xRenderCache=new Map),n._xRenderOriginalText||(n._xRenderOriginalText=new WeakMap),n._xIfData||(n._xIfData=new WeakMap),n._cloneContext||(n._cloneContext=new WeakMap),n._xClassCache||(n._xClassCache=new Map),n._xForCache||(n._xForCache=new WeakMap),n.__mirrorContainer||(n.__mirrorContainer=o.createDocumentFragment());const p={};function d(e){let t=e;for(;t;){const e=n._cloneContext.get(t);if(e)return n.getAttribute("logs")&&n.getAttribute("logs").includes("plugins")&&console.log("Found context for element:",t,e),e;t=t.parentNode}return n.getAttribute("logs")&&n.getAttribute("logs").includes("plugins")&&console.log("Using default component context:",n),n}function h(e,r){if(!e)return!0;const n=d(r);try{return t(e,{this:n,...p})}catch(t){return void f(`[evaluate] "${e}" failed in`,n,t)}}Object.getOwnPropertyNames(Object.getPrototypeOf(n)).forEach(e=>{"function"==typeof n[e]&&"constructor"!==e&&(p[e]=n[e].bind(n))}),p.plugins=r;const x=i&&c("for");x&&console.groupCollapsed("x-for");Array.from(e.querySelectorAll("template[x-for]")).concat(Array.from(n.__mirrorContainer.querySelectorAll("template[x-for]"))).forEach(e=>{let t,r,s;e._xForMeta||(e._xForMeta={parent:e.parentNode,next:e.nextSibling},n.__mirrorContainer.appendChild(e));try{({loopVar:t,indexVar:r,arrayPath:s}=function(e){const t=e.match(/^\s*(?:\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*\)|([^,\s()]+))\s+(?:in|of)\s+(.+)$/);if(!t)throw new Error("Invalid x-for: "+e);return{loopVar:t[1]||t[3],indexVar:t[2]||null,arrayPath:t[4].trim()}}(e.getAttribute("x-for")))}catch(e){return void f("[x-for]",e.message)}const a=function(e,t){const r=d(t),n=e.match(/([^[.\]]+)|\[(\d+)\]/g);if(n)return n.reduce((e,t)=>{if(null!=e){if(t.startsWith("[")){const r=parseInt(t.slice(1,-1),10);return Array.isArray(e)?e[r]:void 0}return e[t]}},r)}(s,e);if(!Array.isArray(a))return void f(`[x-for] expected array at ${s}`,a);x&&console.log(`iterating ${s} → length ${a.length}`);const i=function(e){return JSON.stringify(e)}(a),l=n._xForCache.get(e);l&&l.arrayRef===a&&l.length===a.length&&l.signature===i?x&&console.log(" → skipped (no change)"):(n._xForCache.set(e,{arrayRef:a,length:a.length,signature:i}),e._forClones&&e._forClones.forEach(e=>e.remove()),e._forClones=[],a.forEach((s,a)=>{x&&console.groupCollapsed(` index ${a}`);const i=o.createDocumentFragment();i.appendChild(e.content.cloneNode(!0));const l=(c=t,u=s,f=r,p=a,new Proxy(n,{has:(e,t)=>t===c||f&&t===f||t in e,get:(e,t)=>t===c?u:f&&t===f?p:e[t]}));var c,u,f,p,d,h;d=i,h=l,n._cloneContext.set(d,h),d.querySelectorAll("*").forEach(e=>n._cloneContext.set(e,h));const g=n._xRenderCache,y=n._xRenderOriginalText;for(n._xRenderCache=new Map,n._xRenderOriginalText=new WeakMap,rendux.call(n,i),n._xRenderCache=g,n._xRenderOriginalText=y;i.firstChild;){const t=i.firstChild;e._xForMeta.parent.insertBefore(t,e._xForMeta.next),e._forClones.push(t)}x&&console.groupEnd()}))}),x&&console.groupEnd();const g=i&&c("if");Array.from(e.querySelectorAll("[x-if]")).concat(Array.from(n.__mirrorContainer.querySelectorAll("[x-if]"))).forEach(e=>{const t=e.getAttribute("x-if")?.trim(),r=!t||h(t,e);g&&console.groupCollapsed("x-if",e,t,"→",r);let s=n._xIfData.get(e);s||(s={placeholder:o.createComment("x-if placeholder"),isHidden:!1},n._xIfData.set(e,s)),r||s.isHidden?r&&s.isHidden&&(s.placeholder.parentNode.replaceChild(e,s.placeholder),s.isHidden=!1):(e.parentNode.replaceChild(s.placeholder,e),n.__mirrorContainer.appendChild(e),s.isHidden=!0),g&&console.groupEnd()}),0===n._xRenderCache.size&&Array.from(e.querySelectorAll("[render]")).forEach(e=>{const t=e.getAttribute("render")?.trim();t&&(n._xRenderCache.set(e,t),n._xRenderOriginalText.set(e,e.textContent))}),Array.from(e.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null==t||n._xClassCache.has(e)||n._xClassCache.set(e,t)});const y=i&&c("attr");Array.from(e.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(t=>{if(t.name.startsWith("x-")&&!["x-for","x-key","x-class","x-if","x-hidden"].includes(t.name)){const o=t.name,s=r.get(o);if(s&&"attribute"===s.target){const r=h(t.value.trim(),e);try{u&&console.log("[plugin]",`Executing ${o} with:`,{element:e,value:r,component:n}),s.execute(e,r,n)}catch(e){u&&console.error("[plugin]",`Error executing ${o}:`,e)}}else{const r=t.name.slice(2),n=h(t.value.trim(),e);y&&console.groupCollapsed("x-attr",e,r,"=",n),n?e.setAttribute(r,String(n)):e.removeAttribute(r),y&&console.groupEnd()}}})});const m=i&&c("class");m&&console.groupCollapsed("x-class");for(const[e,t]of n._xClassCache){m&&console.log("element →",e);const r=t.trim();if(r.includes("(")){const t=/\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)/g;n._xClassDynamicValues||(n._xClassDynamicValues=new Map);(n._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));const o=new Set;let s;for(;s=t.exec(r);){const t=s[1].trim(),r=s[2].trim();let n;n=/^[\w\-\s]+$/.test(t)?t:h(t,e);const a=Boolean(h(r,e));let i=[];"string"==typeof n?i=n.split(/\s+/).filter(Boolean):Array.isArray(n)?i=n:n&&"object"==typeof n&&(i=Object.keys(n).filter(e=>n[e])),a&&i.forEach(t=>e.classList.add(t)),i.forEach(e=>o.add(e)),m&&console.log(` ${t} → [${i.join(" ")}] (= ${a})`)}n._xClassDynamicValues.set(e,Array.from(o))}else{n._xClassDynamicValues||(n._xClassDynamicValues=new Map);let t;(n._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));let o=!0;if(!r.includes(",")&&/[?:]/.test(r))t=h(r,e);else{const n=r.indexOf(","),s=n>=0?r.slice(0,n).trim():r,a=n>=0?r.slice(n+1).trim():"true";o=Boolean(h(a,e)),t=s}const s=[];"string"==typeof t?s.push(...t.split(/\s+/).filter(Boolean)):Array.isArray(t)?s.push(...t):t&&"object"==typeof t&&s.push(...Object.keys(t).filter(e=>t[e])),s.forEach(t=>{o?e.classList.add(t):e.classList.remove(t)}),m&&console.log(` x-class ${r} →`,s,`ok=${o}`),n._xClassDynamicValues.set(e,s)}}m&&console.groupEnd();const _=i&&c("render");_&&console.groupCollapsed("render");for(const[e,t]of n._xRenderCache){const r=n._xIfData.get(e);if(r&&r.isHidden)continue;_&&console.log("element →",e,"expr=",t);const o=t.indexOf(","),s=o<0?t:t.slice(0,o).trim(),a=h(o<0?"true":t.slice(o+1).trim(),e);let i;if(a){let t=h(s,e);if(void 0===t)i=n._xRenderOriginalText.get(e)||"";else if(null!=t&&"object"==typeof t)try{i=JSON.stringify(t,null,2)}catch{i=String(t)}else i=null==t||"boolean"==typeof t?"":String(t)}else i=n._xRenderOriginalText.get(e)||"";e.textContent!==i&&(e.textContent=i),_&&console.log(` → "${i}" (cond=${a})`)}_&&console.groupEnd(),Array.from(e.querySelectorAll("*")).forEach(e=>{const t=n._xIfData.get(e);t&&t.isHidden||[["render","render"]].forEach(([t,r])=>{e.hasAttribute(t)&&!c(r)&&e.removeAttribute(t)})}),function e(r){Array.from(r.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(r=>{if(r.name.startsWith("@")){const o=r.name.slice(1),s=r.value.trim();e._xEventListeners&&e._xEventListeners[o]&&e.removeEventListener(o,e._xEventListeners[o]),e._xEventListeners||(e._xEventListeners={});const a=d(e),i=new Proxy(a,{get:(e,t)=>t in e?e[t]:n[t]}),l=function(e){try{return t(s,{this:i,event:e,...p})}catch(e){f(`[rendux @${o}] Error evaluating: ${s}`,e)}};e.addEventListener(o,l),e._xEventListeners[o]=l}})}),Array.from(r.querySelectorAll("slot")).forEach(t=>{(t.assignedElements?t.assignedElements({flatten:!0}):[]).forEach(t=>{e(t)})})}(e),s(e,n)}rendux.use=function(e){return n(e),rendux},"undefined"!=typeof module&&module.exports&&(module.exports={rendux:rendux,use:n,process:s,plugins:r,parsePluginCall:o});export{o as parsePluginCall,r as plugins,s as process,rendux,n as use};
1
+ /*! rendux v0.93.3 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
+ const e={Math:Math,Date:Date,Number:Number,String:String,Boolean:Boolean,JSON:JSON,parseInt:parseInt,parseFloat:parseFloat,isFinite:isFinite,Array:Array};function t(t,r={}){const n=function(e){const t=[];let r=0;for(;r<e.length;){let n=e[r];if(/\s/.test(n)){r++;continue}if('"'===n||"'"===n){const o=n;let s="";for(r++;r<e.length&&e[r]!==o;)"\\"===e[r]?(r++,r<e.length&&(s+=e[r++])):s+=e[r++];r++,t.push({type:"string",value:s});continue}if(/[0-9]/.test(n)||"."===n&&/[0-9]/.test(e[r+1])){let n=r;for(;r<e.length&&/[0-9]/.test(e[r]);)r++;if("."===e[r])for(r++;r<e.length&&/[0-9]/.test(e[r]);)r++;const o=parseFloat(e.slice(n,r));t.push({type:"number",value:o});continue}if(/[a-zA-Z_$]/.test(n)){let n=r;for(r++;r<e.length&&/[a-zA-Z0-9_$]/.test(e[r]);)r++;const o=e.slice(n,r);"true"===o||"false"===o?t.push({type:"boolean",value:"true"===o}):"null"===o?t.push({type:"null",value:null}):t.push({type:"identifier",value:o});continue}const o=e.substr(r,2),s=e.substr(r,3);if(["===","!=="].includes(s))t.push({type:"operator",value:s}),r+=3;else if(["==","!=",">=","<=","&&","||"].includes(o))t.push({type:"operator",value:o}),r+=2;else{if(!["+","-","*","/","%",">","<","!","?",":","(",")","[","]",".",","].includes(n))throw new Error(`Invalid character '${n}' in expression`);t.push({type:"operator",value:n}),r++}}return t.push({type:"EOF"}),t}(t);let o=0;function s(){return n[o]||{type:"EOF"}}function l(){return n[o++]||{type:"EOF"}}function a(e,t){const r=s();if(r.type!==e||void 0!==t&&r.value!==t)throw new Error(`Expected ${t||e} but got ${r.value}`);return l(),r}function i(e){const t=s();return"operator"===t.type&&t.value===e}const c=u();if("EOF"!==s().type)throw new Error(`Unexpected token: ${s().value}`);return c;function u(){let e=function(){let e=f();for(;i("||");){l();const t=f();e=e||t}return e}();if(i("?")){l();const t=u();a("operator",":");const r=u();e=e?t:r}return e}function f(){let e=d();for(;i("&&");){l();const t=d();e=e&&t}return e}function d(){let e=h();for(;["==","!=","===","!=="].includes(s().value);){const t=l().value,r=h();switch(t){case"==":e=e==r;break;case"!=":e=e!=r;break;case"===":e=e===r;break;case"!==":e=e!==r}}return e}function h(){let e=p();for(;[">","<",">=","<="].includes(s().value);){const t=l().value,r=p();switch(t){case">":e=e>r;break;case"<":e=e<r;break;case">=":e=e>=r;break;case"<=":e=e<=r}}return e}function p(){let e=x();for(;["+","-"].includes(s().value);){const t=l().value,r=x();e="+"===t?e+r:e-r}return e}function x(){let e=g();for(;["*","/","%"].includes(s().value);){const t=l().value,r=g();switch(t){case"*":e*=r;break;case"/":e/=r;break;case"%":e%=r}}return e}function g(){if(["!","+","-"].includes(s().value)){const e=l().value,t=g();switch(e){case"!":return!t;case"+":return+t;case"-":return-t}}return function(){const t=s();if("number"===t.type||"string"===t.type||"boolean"===t.type||"null"===t.type)return l(),t.value;if("identifier"===t.type){l();let n=function(t){if(t in r)return r[t];if(t in e)return e[t];if(null!=r.this&&t in r.this)return r.this[t];throw new Error(`Unknown identifier: ${t}`)}(t.value);for(;;)if(i(".")){l();const e=a("identifier").value;n=null==n?void 0:n[e]}else if(i("[")){l();const e=u();a("operator","]"),n=null==n?void 0:n[e]}else{if(!i("("))break;{l();const e=[];if(!i(")"))do{e.push(u())}while(i(",")&&l());if(a("operator",")"),"function"!=typeof n)throw new Error(`'${t.value}' is not a function`);n=n.apply(r.this,e)}}return n}if(i("(")){l();const e=u();return a("operator",")"),e}throw new Error(`Unexpected token: ${t.value}`)}()}}const r=new Map;function n(e){if(!e||!e.name)throw new Error("Invalid plugin");r.set(e.name,e)}function o(e,t={}){const n=e.match(/^([a-zA-Z_$][\w$]*)\s*\(\s*(.*)\s*\)$/);if(!n)return null;const o=n[1],s=r.get(o);if(!s)return null;const l=n[2].trim();if(""===l)return{plugin:s,args:[]};return{plugin:s,args:l.split(/\s*,\s*/).map(e=>{if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);if(/^-?\d+(?:\.\d+)?$/.test(e))return parseFloat(e);if(e in t)return t[e];throw new Error(`Unknown identifier: ${e}`)})}}async function s(e=document,t={}){const n=Array.from(e.querySelectorAll("*")).filter(e=>Array.from(e.attributes).some(e=>e.name.startsWith("render.")));for(const e of n)for(const n of Array.from(e.attributes)){if(!n.name.startsWith("render."))continue;const s=n.name,l=n.value.trim();let a,i,c;if("render.plugin"===s){let e;try{e=o(l,t)}catch(e){console.error(e);continue}if(!e)continue;a=e.plugin,i=e.args}else{const e=s.slice(7);if(a=r.get(e),!a)continue;if(l.startsWith(e+"(")){let e;try{e=o(l,t)}catch(e){console.error(e);continue}if(!e)continue;a=e.plugin,i=e.args}else i=[l]}"function"==typeof a.onBeforeExecute&&a.onBeforeExecute(e,l,t);try{c=a.execute(...i)}catch(e){continue}e.textContent=null==c?"":String(c),"function"==typeof a.onAfterExecute&&a.onAfterExecute(e,l,t)}for(const n of r.values())"function"==typeof n.onAfterRender&&await n.onAfterRender(e,t)}function rendux(e,n){const o=e,l=n;let a=n.shadowRoot||n;if(!a)throw new Error("rendux requires a DOM element as the second argument");for(const e of r.values())"function"==typeof e.onBeforeRender&&e.onBeforeRender(a,o);const i=document,c=l.getAttribute&&l.getAttribute("logs"),u=Boolean(c),f=c?new Set(c.split(",").map(e=>e.trim().toLowerCase())):new Set,d=e=>f.has("all")||f.has(e.toLowerCase())||f.has(("x-"+e).toLowerCase()),h=u&&f.has("plugins"),p=(...e)=>console.warn(...e);l._xRenderCache||(l._xRenderCache=new Map),l._xRenderOriginalText||(l._xRenderOriginalText=new WeakMap),l._xIfData||(l._xIfData=new WeakMap),l._cloneContext||(l._cloneContext=new WeakMap),l._xClassCache||(l._xClassCache=new Map),l._xForCache||(l._xForCache=new WeakMap),l.__mirrorContainer||(l.__mirrorContainer=i.createDocumentFragment());const x={};function g(e){let t=e;for(;t;){const e=l._cloneContext.get(t);if(e)return l.getAttribute&&l.getAttribute("logs")&&l.getAttribute("logs").includes("plugins")&&console.log("Found context for element:",t,e),e;t=t.parentNode}return l.getAttribute&&l.getAttribute("logs")&&l.getAttribute("logs").includes("plugins")&&console.log("Using default component context:",o),o}function y(e,r){if(!e)return!0;const n=g(r);try{return t(e,{this:n,...x})}catch(t){return void p(`[evaluate] "${e}" failed in`,n,t)}}function m(e,t){const r=g(t),n=e.match(/([^[.\]]+)|\[(\d+)\]/g);if(n)return n.reduce((e,t)=>{if(null!=e){if(t.startsWith("[")){const r=parseInt(t.slice(1,-1),10);return Array.isArray(e)?e[r]:void 0}return e[t]}},r)}function A(e){const t=e.match(/^\s*(?:\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*\)|([^,\s()]+))\s+(?:in|of)\s+(.+)$/);if(!t)throw new Error("Invalid x-for: "+e);return{loopVar:t[1]||t[3],indexVar:t[2]||null,arrayPath:t[4].trim()}}function C(e,t,r,n,o){return new Proxy(e,{has:(e,r)=>r===t||n&&r===n||r in e,get:(e,s)=>s===t?r:n&&s===n?o:e[s]})}function _(e,t){l._cloneContext.set(e,t),e.querySelectorAll("*").forEach(e=>l._cloneContext.set(e,t))}l&&Object.getPrototypeOf(l)&&Object.getOwnPropertyNames(Object.getPrototypeOf(l)).forEach(e=>{"function"==typeof l[e]&&"constructor"!==e&&(x[e]=l[e].bind(l))}),x.plugins=r;const E=u&&d("for");function b(e){try{const t=new WeakSet;return JSON.stringify(e,(e,r)=>{if("function"!=typeof r&&!e.startsWith("$")){if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r}})}catch(t){return`${e.length}-${e[0]?.name||""}-${e[e.length-1]?.name||""}`}}E&&console.groupCollapsed("x-for");let v=new Set,w=!0;for(;w;){const e=Array.from(a.querySelectorAll("template[x-for]")).concat(Array.from(l.__mirrorContainer.querySelectorAll("template[x-for]"))).filter(e=>!v.has(e));if(0===e.length){w=!1;break}e.forEach(e=>{let r,n,s;v.add(e),e._xForMeta||(e._xForMeta={parent:e.parentNode,next:e.nextSibling},l.__mirrorContainer.appendChild(e));try{({loopVar:r,indexVar:n,arrayPath:s}=A(e.getAttribute("x-for")))}catch(e){return void p("[x-for]",e.message)}const a=m(s,e);if(!Array.isArray(a))return void p(`[x-for] expected array at ${s}`,a);E&&console.log(`iterating ${s} → length ${a.length}`);const c=e.content.cloneNode(!0).querySelector("*"),u=c&&c.hasAttribute("x-key");if(!u){const t=b(a),r=l._xForCache.get(e);if(r&&r.arrayRef===a&&r.length===a.length&&r.signature===t)return void(E&&console.log(" → skipped (no change)"));l._xForCache.set(e,{arrayRef:a,length:a.length,signature:t})}if(u&&e._forClones&&e._forClones.length>0){E&&console.log(" → using x-key diffing");const s=c.getAttribute("x-key"),u=new Map;e._forClones.forEach(e=>{if(1===e.nodeType){const t=e.getAttribute("x-key");t&&u.set(t,e)}});const f=[],d=new Set;a.forEach((a,c)=>{E&&console.groupCollapsed(` index ${c}`);const h=C(o,r,a,n,c);let g;try{g=t(s,{this:h,...x})}catch(e){p("[x-for] Error evaluating x-key:",e),g=c}const y=String(g);if(u.has(y)){const e=u.get(y);d.add(e),E&&console.log(` → reusing node for key: ${y}`),l._cloneContext.set(e,h),f.push(e)}else{E&&console.log(` → creating new node for key: ${y}`);const t=i.createDocumentFragment();t.appendChild(e.content.cloneNode(!0));const r=t.firstElementChild;if(r){t.removeChild(r),r.setAttribute("x-key",y),_(r,h);(r.hasAttribute("render")?[r,...r.querySelectorAll("[render]")]:r.querySelectorAll("[render]")).forEach(e=>{const t=e.getAttribute("render")?.trim();t&&(l._xRenderCache.set(e,t),l._xRenderOriginalText.set(e,e.textContent))});(r.hasAttribute("x-class")?[r,...r.querySelectorAll("[x-class]")]:r.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null!=t&&l._xClassCache.set(e,t)}),f.push(r)}}E&&console.groupEnd()}),e._forClones.forEach(e=>{if(!d.has(e)){if(E&&console.log(" → removing unused node"),1===e.nodeType){(e.hasAttribute("render")?[e,...e.querySelectorAll("[render]")]:e.querySelectorAll("[render]")).forEach(e=>{l._xRenderCache.delete(e),l._xRenderOriginalText.delete(e)});(e.hasAttribute("x-class")?[e,...e.querySelectorAll("[x-class]")]:e.querySelectorAll("[x-class]")).forEach(e=>{l._xClassCache.delete(e),l._xClassDynamicValues.delete(e)})}e.remove()}}),f.forEach((t,r)=>{const n=e._xForMeta.parent,o=r<f.length-1?f[r+1]:e._xForMeta.next;t.parentNode&&t.nextSibling===o||n.insertBefore(t,o)}),e._forClones=f}else E&&u&&console.log(" → first render with x-key"),E&&!u&&console.log(" → no x-key, full re-render"),e._forClones&&e._forClones.forEach(e=>{if(1===e.nodeType){(e.hasAttribute("render")?[e,...e.querySelectorAll("[render]")]:e.querySelectorAll("[render]")).forEach(e=>{l._xRenderCache.delete(e),l._xRenderOriginalText.delete(e)});(e.hasAttribute("x-class")?[e,...e.querySelectorAll("[x-class]")]:e.querySelectorAll("[x-class]")).forEach(e=>{l._xClassCache.delete(e),l._xClassDynamicValues.delete(e)})}e.remove()}),e._forClones=[],a.forEach((s,a)=>{E&&console.groupCollapsed(` index ${a}`);const c=i.createDocumentFragment();c.appendChild(e.content.cloneNode(!0));const f=C(o,r,s,n,a);if(_(c,f),u){const e=c.firstElementChild;if(e){const r=e.getAttribute("x-key");if(r)try{const n=t(r,{this:f,...x});e.setAttribute("x-key",String(n))}catch(t){e.setAttribute("x-key",String(a))}}}for(;c.firstChild;){const t=c.firstChild;if(e._xForMeta.parent.insertBefore(t,e._xForMeta.next),e._forClones.push(t),1===t.nodeType){(t.hasAttribute("render")?[t,...t.querySelectorAll("[render]")]:t.querySelectorAll("[render]")).forEach(e=>{const t=e.getAttribute("render")?.trim();t&&(l._xRenderCache.set(e,t),l._xRenderOriginalText.set(e,e.textContent))});(t.hasAttribute("x-class")?[t,...t.querySelectorAll("[x-class]")]:t.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null!=t&&l._xClassCache.set(e,t)})}}E&&console.groupEnd()})})}E&&console.groupEnd();const S=u&&d("if");Array.from(a.querySelectorAll("[x-if]")).concat(Array.from(l.__mirrorContainer.querySelectorAll("[x-if]"))).forEach(e=>{const t=e.getAttribute("x-if")?.trim(),r=!t||y(t,e);S&&console.groupCollapsed("x-if",e,t,"→",r);let n=l._xIfData.get(e);n||(n={placeholder:i.createComment("x-if placeholder"),isHidden:!1},l._xIfData.set(e,n)),r||n.isHidden?r&&n.isHidden&&(n.placeholder.parentNode.replaceChild(e,n.placeholder),n.isHidden=!1):(e.parentNode.replaceChild(n.placeholder,e),l.__mirrorContainer.appendChild(e),n.isHidden=!0),S&&console.groupEnd()}),Array.from(a.querySelectorAll("[render]")).forEach(e=>{if(!l._xRenderCache.has(e)){const t=e.getAttribute("render")?.trim();t&&(l._xRenderCache.set(e,t),l._xRenderOriginalText.set(e,e.textContent))}}),Array.from(a.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null==t||l._xClassCache.has(e)||l._xClassCache.set(e,t)});const $=u&&d("attr");Array.from(a.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(t=>{if(t.name.startsWith("x-")&&!["x-for","x-key","x-class","x-if","x-hidden"].includes(t.name)){const n=t.name,s=r.get(n);if(s&&"attribute"===s.target){const r=y(t.value.trim(),e);try{h&&console.log("[plugin]",`Executing ${n} with:`,{element:e,value:r,component:o}),s.execute(e,r,o)}catch(e){h&&console.error("[plugin]",`Error executing ${n}:`,e)}}else{const r=t.name.slice(2),n=y(t.value.trim(),e);$&&console.groupCollapsed("x-attr",e,r,"=",n),n?e.setAttribute(r,String(n)):e.removeAttribute(r),$&&console.groupEnd()}}})});const k=u&&d("class");k&&console.groupCollapsed("x-class");for(const[e,t]of l._xClassCache){k&&console.log("element →",e);const r=t.trim();if(r.includes("(")){const t=/\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)/g;l._xClassDynamicValues||(l._xClassDynamicValues=new Map);(l._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));const n=new Set;let o;for(;o=t.exec(r);){const t=o[1].trim(),r=o[2].trim();let s;s=/^[\w\-\s]+$/.test(t)?t:y(t,e);const l=Boolean(y(r,e));let a=[];"string"==typeof s?a=s.split(/\s+/).filter(Boolean):Array.isArray(s)?a=s:s&&"object"==typeof s&&(a=Object.keys(s).filter(e=>s[e])),l&&a.forEach(t=>e.classList.add(t)),a.forEach(e=>n.add(e)),k&&console.log(` ${t} → [${a.join(" ")}] (= ${l})`)}l._xClassDynamicValues.set(e,Array.from(n))}else{l._xClassDynamicValues||(l._xClassDynamicValues=new Map);let t;(l._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));let n=!0;if(!r.includes(",")&&/[?:]/.test(r))t=y(r,e);else{const o=r.indexOf(","),s=o>=0?r.slice(0,o).trim():r,l=o>=0?r.slice(o+1).trim():"true";n=Boolean(y(l,e)),t=s}const o=[];"string"==typeof t?o.push(...t.split(/\s+/).filter(Boolean)):Array.isArray(t)?o.push(...t):t&&"object"==typeof t&&o.push(...Object.keys(t).filter(e=>t[e])),o.forEach(t=>{n?e.classList.add(t):e.classList.remove(t)}),k&&console.log(` x-class ${r} →`,o,`ok=${n}`),l._xClassDynamicValues.set(e,o)}}k&&console.groupEnd();const q=u&&d("render");q&&console.groupCollapsed("render");for(const[e,t]of l._xRenderCache){const r=l._xIfData.get(e);if(r&&r.isHidden)continue;q&&console.log("element →",e,"expr=",t);const n=t.indexOf(","),o=n<0?t:t.slice(0,n).trim(),s=y(n<0?"true":t.slice(n+1).trim(),e);let a;if(s){let t=y(o,e);if(void 0===t)a=l._xRenderOriginalText.get(e)||"";else if(null!=t&&"object"==typeof t)try{a=JSON.stringify(t,null,2)}catch{a=String(t)}else a=null==t||"boolean"==typeof t?"":String(t)}else a=l._xRenderOriginalText.get(e)||"";e.textContent!==a&&(e.textContent=a),q&&console.log(` → "${a}" (cond=${s})`)}q&&console.groupEnd(),Array.from(a.querySelectorAll("*")).forEach(e=>{const t=l._xIfData.get(e);t&&t.isHidden||[["render","render"]].forEach(([t,r])=>{e.hasAttribute(t)&&!d(r)&&e.removeAttribute(t)})}),function e(r){Array.from(r.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(r=>{if(r.name.startsWith("@")){const n=r.name.slice(1),s=r.value.trim();e._xEventListeners&&e._xEventListeners[n]&&e.removeEventListener(n,e._xEventListeners[n]),e._xEventListeners||(e._xEventListeners={});const l=g(e),a=new Proxy(l,{get:(e,t)=>t in e?e[t]:o[t]}),i=function(e){try{return t(s,{this:a,event:e,...x})}catch(e){p(`[rendux @${n}] Error evaluating: ${s}`,e)}};e.addEventListener(n,i),e._xEventListeners[n]=i}})}),Array.from(r.querySelectorAll("slot")).forEach(t=>{(t.assignedElements?t.assignedElements({flatten:!0}):[]).forEach(t=>{e(t)})})}(a),s(a,o)}rendux.use=function(e){return n(e),rendux},"undefined"!=typeof module&&module.exports&&(module.exports={rendux:rendux,use:n,process:s,plugins:r,parsePluginCall:o});export{o as parsePluginCall,r as plugins,s as process,rendux,n as use};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynck/rendux",
3
- "version": "0.92.1",
3
+ "version": "0.93.3",
4
4
  "type": "module",
5
5
  "main": "./dist/rendux.cjs.min.js",
6
6
  "module": "./dist/rendux.min.js",
@@ -8,14 +8,6 @@
8
8
  ".": {
9
9
  "import": "./dist/rendux.min.js",
10
10
  "require": "./dist/rendux.cjs.min.js"
11
- },
12
- "./plugin/x-tooltip": {
13
- "import": "./dist/plugin/x-tooltip.min.js",
14
- "require": "./dist/plugin/x-tooltip.cjs.min.js"
15
- },
16
- "./plugin/i18n": {
17
- "import": "./dist/plugin/i18n.min.js",
18
- "require": "./dist/plugin/i18n.cjs.min.js"
19
11
  }
20
12
  },
21
13
  "author": "Yannick J.A. CHARLERY",
@@ -1,2 +0,0 @@
1
- /*! plugin/i18n v0.92.1 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={en:{message:"Welcome to rendux!",button:"Click me",title:"Hello World"},fr:{message:"Bienvenue dans rendux!",button:"Cliquez-moi",title:"Bonjour le Monde"}};const t=function(t={}){const n=t.translations||e;let o=t.defaultLanguage||"en";return{name:"i18n",target:"render",execute:function(e,...t){const r=e.split(".");let s=n[o];for(const t of r){if(!s||"object"!=typeof s||!(t in s))return`[${e}]`;s=s[t]}if("string"==typeof s&&t.length>0){return s.replace(/\{(\d+)\}/g,(e,n)=>{const o=parseInt(n,10);return void 0!==t[o]?t[o]:e})}return s||`[${e}]`},setLanguage:e=>!!n[e]&&(o=e,!0),getLanguage:()=>o,addTranslations(e,t){n[e]||(n[e]={}),Object.assign(n[e],t)}}};exports.i18nPlugin=t;
@@ -1,2 +0,0 @@
1
- /*! plugin/i18n v0.92.1 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
- const e={en:{message:"Welcome to rendux!",button:"Click me",title:"Hello World"},fr:{message:"Bienvenue dans rendux!",button:"Cliquez-moi",title:"Bonjour le Monde"}};const t=function(t={}){const n=t.translations||e;let o=t.defaultLanguage||"en";return{name:"i18n",target:"render",execute:function(e,...t){const r=e.split(".");let s=n[o];for(const t of r){if(!s||"object"!=typeof s||!(t in s))return`[${e}]`;s=s[t]}if("string"==typeof s&&t.length>0){return s.replace(/\{(\d+)\}/g,(e,n)=>{const o=parseInt(n,10);return void 0!==t[o]?t[o]:e})}return s||`[${e}]`},setLanguage:e=>!!n[e]&&(o=e,!0),getLanguage:()=>o,addTranslations(e,t){n[e]||(n[e]={}),Object.assign(n[e],t)}}};export{t as i18nPlugin};
@@ -1,2 +0,0 @@
1
- /*! plugin/x-logger v0.92.1 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={name:"logger",target:"render",execute:function(e,...r){switch(e){case"logMessage":return`[Message: ${r.join(", ")}]`;case"logError":return`[Error: ${r.join(", ")}]`;case"logWarning":return`[Warning: ${r.join(", ")}]`;case"logInfo":return`[Info: ${r.join(", ")}]`;case"logDebug":return`[Debug: ${r.join(", ")}]`;case"logTable":return r.length>0&&(Array.isArray(r[0])||"object"==typeof r[0])?`[Table: ${Array.isArray(r[0])?r[0].length+" items":"object"}]`:`[Table: ${r.join(", ")}]`;default:return`[${e}: ${r.join(", ")}]`}}},r=e;exports.logger=e,exports.loggerPlugin=r;
@@ -1,2 +0,0 @@
1
- /*! plugin/x-logger v0.92.1 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
- const e={name:"logger",target:"render",execute:function(e,...r){switch(e){case"logMessage":return`[Message: ${r.join(", ")}]`;case"logError":return`[Error: ${r.join(", ")}]`;case"logWarning":return`[Warning: ${r.join(", ")}]`;case"logInfo":return`[Info: ${r.join(", ")}]`;case"logDebug":return`[Debug: ${r.join(", ")}]`;case"logTable":return r.length>0&&(Array.isArray(r[0])||"object"==typeof r[0])?`[Table: ${Array.isArray(r[0])?r[0].length+" items":"object"}]`:`[Table: ${r.join(", ")}]`;default:return`[${e}: ${r.join(", ")}]`}}},r=e;export{e as logger,r as loggerPlugin};
@@ -1,2 +0,0 @@
1
- /*! plugin/x-tooltip v0.92.1 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={name:"x-tooltip",target:"attribute",execute:function(e,t,o){e._xTooltipElement&&e._xTooltipElement.remove();const n=document.createElement("div");n.textContent=t,n.style.cssText="\n position: absolute;\n background: #333;\n color: white;\n padding: 8px 12px;\n border-radius: 4px;\n font-size: 12px;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n ",document.body.appendChild(n),e._xTooltipElement=n;const i=function(t){const o=e.getBoundingClientRect(),i=window.pageXOffset||document.documentElement.scrollLeft,s=window.pageYOffset||document.documentElement.scrollTop,l=i+o.left+(o.width-n.offsetWidth)/2,p=s+o.top-n.offsetHeight-8;n.style.left=`${l}px`,n.style.top=`${p}px`,n.style.opacity="1"},s=function(){n.style.opacity="0"};if(e._xTooltipListeners&&(e.removeEventListener("mouseenter",e._xTooltipListeners.show),e.removeEventListener("mouseleave",e._xTooltipListeners.hide)),e.addEventListener("mouseenter",i),e.addEventListener("mouseleave",s),e._xTooltipListeners={show:i,hide:s},!e._xTooltipCleanup){e._xTooltipCleanup=!0;const t=new MutationObserver(o=>{o.forEach(o=>{o.removedNodes.forEach(o=>{(o===e||o.contains&&o.contains(e))&&(e._xTooltipElement&&e._xTooltipElement.remove(),t.disconnect())})})});t.observe(document.body,{childList:!0,subtree:!0})}}};exports.xTooltip=e;
@@ -1,2 +0,0 @@
1
- /*! plugin/x-tooltip v0.92.1 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
2
- const e={name:"x-tooltip",target:"attribute",execute:function(e,t,o){e._xTooltipElement&&e._xTooltipElement.remove();const n=document.createElement("div");n.textContent=t,n.style.cssText="\n position: absolute;\n background: #333;\n color: white;\n padding: 8px 12px;\n border-radius: 4px;\n font-size: 12px;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n ",document.body.appendChild(n),e._xTooltipElement=n;const i=function(t){const o=e.getBoundingClientRect(),i=window.pageXOffset||document.documentElement.scrollLeft,s=window.pageYOffset||document.documentElement.scrollTop,l=i+o.left+(o.width-n.offsetWidth)/2,p=s+o.top-n.offsetHeight-8;n.style.left=`${l}px`,n.style.top=`${p}px`,n.style.opacity="1"},s=function(){n.style.opacity="0"};if(e._xTooltipListeners&&(e.removeEventListener("mouseenter",e._xTooltipListeners.show),e.removeEventListener("mouseleave",e._xTooltipListeners.hide)),e.addEventListener("mouseenter",i),e.addEventListener("mouseleave",s),e._xTooltipListeners={show:i,hide:s},!e._xTooltipCleanup){e._xTooltipCleanup=!0;const t=new MutationObserver(o=>{o.forEach(o=>{o.removedNodes.forEach(o=>{(o===e||o.contains&&o.contains(e))&&(e._xTooltipElement&&e._xTooltipElement.remove(),t.disconnect())})})});t.observe(document.body,{childList:!0,subtree:!0})}}};export{e as xTooltip};